hibernate/hibernate-orm · error · LocalSynchronizationException
Exception calling user Synchronization (afterCompletion): ${
Error message
Exception calling user Synchronization (afterCompletion): ${synchronization.getClass().getName()} What it means
While notifying registered Synchronizations that the transaction completed (with a Status code), one afterCompletion(status) call threw a Throwable. Hibernate wraps it in LocalSynchronizationException naming the failing class; the finally block still clears the synchronization set. Note the JTA contract says afterCompletion should never throw — an exception here surfaces from the commit/completion call and can mask the transaction's real outcome.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/resource/transaction/internal/SynchronizationRegistryStandardImpl.java:80
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) {
SYNCHRONIZATION_LOGGER.synchronizationFailed( synchronization, t );
throw new LocalSynchronizationException(
"Exception calling user Synchronization (afterCompletion): " + synchronization.getClass().getName(),
t
);
}
}
}
finally {
clearSynchronizations();
}
}
}
@Override
public void clearSynchronizations() {
SYNCHRONIZATION_LOGGER.clearingSynchronizations();
if ( synchronizations != null ) {
synchronizations.clear();
}View on GitHub (pinned to fad1729dce)
Solutions
- Make afterCompletion best-effort: catch everything inside the callback and log instead of rethrowing
- Read the status argument (javax.transaction.Status) and branch instead of assuming commit
- Inspect the wrapped cause to find which external resource failed and fix that resource's lifecycle
Example fix
// before
public void afterCompletion(int status) {
jmsSession.close(); // throws JMSException -> aborts notification loop
}
// after
public void afterCompletion(int status) {
try {
jmsSession.close();
}
catch (Exception e) {
LOG.warn("failed to close JMS session after tx completion (status={})", status, e);
}
} Defensive patterns
Strategy: try-catch
Try / catch
try {
userTransaction.commit();
}
catch (LocalSynchronizationException e) {
// afterCompletion failed; check tx status to learn the real outcome
int status = userTransaction.getStatus();
LOG.error("afterCompletion callback failed; tx status={}", status, e.getCause());
} Prevention
- Never let afterCompletion throw: catch and log everything inside the callback
- Branch on the status argument instead of assuming commit
- Idempotent cleanup: make release operations safe to re-run
When it happens
Trigger: A user Synchronization.afterCompletion(int status) that releases external resources (JMS, files, locks, HTTP calls) and one of those operations throws; or callback code that dereferences null after a rollback it did not expect.
Common situations: Cleanup callbacks releasing connections or message listeners; code assuming status == STATUS_COMMITTED and failing on rollback; background cleanup interacting with an already-closed Session.
Related errors
- Exception calling user Synchronization (beforeCompletion): $
- 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/3a702613596a3274.
Report an issue: GitHub.