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

  1. Make afterCompletion best-effort: catch everything inside the callback and log instead of rethrowing
  2. Read the status argument (javax.transaction.Status) and branch instead of assuming commit
  3. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/3a702613596a3274. Report an issue: GitHub.