hibernate/hibernate-orm · error · LocalSynchronizationException
Exception calling user Synchronization (beforeCompletion): $
Error message
Exception calling user Synchronization (beforeCompletion): ${synchronization.getClass().getName()} What it means
While iterating registered Synchronizations to call beforeCompletion(), one of them threw a Throwable. Hibernate logs it (synchronizationFailed) and wraps it in LocalSynchronizationException, naming the failing Synchronization class and carrying the original throwable as cause. The loop aborts, so synchronizations after the failing one do not get their beforeCompletion callback.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/resource/transaction/internal/SynchronizationRegistryStandardImpl.java:60
}
final boolean added = synchronizations.add( synchronization );
if ( !added ) {
SYNCHRONIZATION_LOGGER.synchronizationAlreadyRegistered( synchronization );
}
}
@Override
public void notifySynchronizationsBeforeTransactionCompletion() {
SYNCHRONIZATION_LOGGER.notifyingSynchronizationsBefore();
if ( synchronizations != null ) {
for ( var synchronization : synchronizations ) {
try {
synchronization.beforeCompletion();
}
catch (Throwable t) {
SYNCHRONIZATION_LOGGER.synchronizationFailed( synchronization, t );
throw new LocalSynchronizationException(
"Exception calling user Synchronization (beforeCompletion): " + synchronization.getClass().getName(),
t
);
}
}
}
}
@Override
public void notifySynchronizationsAfterTransactionCompletion(int status) {
SYNCHRONIZATION_LOGGER.notifyingSynchronizationsAfter( status );
if ( synchronizations != null ) {
try {
for ( var synchronization : synchronizations ) {
try {
synchronization.afterCompletion( status );
}
catch (Throwable t) {View on GitHub (pinned to fad1729dce)
Solutions
- Inspect the caused-by chain of LocalSynchronizationException — the real failure is the wrapped exception
- Harden the Synchronization: wrap the body of beforeCompletion in try/catch and decide explicitly whether to rethrow
- Move DB work or validation out of beforeCompletion into the business transaction instead
- Verify the callback does not touch a Session that is already closed or used from another thread
Example fix
// before
public class CleanupSync implements Synchronization {
public void beforeCompletion() {
auditMapper.insert(buildAuditRow()); // JDBC failure aborts commit
}
...
}
// after
public void beforeCompletion() {
try {
auditMapper.insert(buildAuditRow());
}
catch (RuntimeException e) {
LOG.error("audit beforeCompletion failed", e); // decide: swallow or rethrow
}
} Defensive patterns
Strategy: try-catch
Try / catch
try {
userTransaction.commit();
}
catch (LocalSynchronizationException e) {
Throwable real = e.getCause(); // the actual beforeCompletion failure
// decide: rollback path, alerting, compensation
} Prevention
- Keep beforeCompletion callbacks free of DB writes and remote calls
- Wrap callback bodies in try/catch and only rethrow when you intend to veto the commit
- Unit-test synchronizations for both commit and rollback paths
When it happens
Trigger: A user-supplied Synchronization.beforeCompletion() that performs a manual flush violating a constraint, executes SQL that fails, or throws any RuntimeException/Error during transaction commit preparation.
Common situations: Callbacks doing validation or DB work in beforeCompletion; optimistic-lock checks implemented as synchronizations; accessing a closed connection or stale EntityManager from the callback; exceptions thrown deliberately to veto a commit.
Related errors
- Exception calling user Synchronization (afterCompletion): ${
- Could not access JTA Transaction to register synchronization
- Synchronization to register cannot be null
- Unable to register cleanup Synchronization with TransactionM
- Transaction is not accessible when using JTA with JPA-compli
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/96d1b48272ee0cfb.
Report an issue: GitHub.