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

  1. Inspect the caused-by chain of LocalSynchronizationException — the real failure is the wrapped exception
  2. Harden the Synchronization: wrap the body of beforeCompletion in try/catch and decide explicitly whether to rethrow
  3. Move DB work or validation out of beforeCompletion into the business transaction instead
  4. 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

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


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