{"record":{"id":"96d1b48272ee0cfb","repo":"hibernate/hibernate-orm","slug":"exception-calling-user-synchronization-beforecomp","errorCode":null,"errorMessage":"Exception calling user Synchronization (beforeCompletion): ${synchronization.getClass().getName()}","messagePattern":"Exception calling user Synchronization \\(beforeCompletion\\): (.+?)","errorType":"exception","errorClass":"LocalSynchronizationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/resource/transaction/internal/SynchronizationRegistryStandardImpl.java","lineNumber":60,"sourceCode":"\t\t}\n\n\t\tfinal boolean added = synchronizations.add( synchronization );\n\t\tif ( !added ) {\n\t\t\tSYNCHRONIZATION_LOGGER.synchronizationAlreadyRegistered( synchronization );\n\t\t}\n\t}\n\n\t@Override\n\tpublic void notifySynchronizationsBeforeTransactionCompletion() {\n\t\tSYNCHRONIZATION_LOGGER.notifyingSynchronizationsBefore();\n\t\tif ( synchronizations != null ) {\n\t\t\tfor ( var synchronization : synchronizations ) {\n\t\t\t\ttry {\n\t\t\t\t\tsynchronization.beforeCompletion();\n\t\t\t\t}\n\t\t\t\tcatch (Throwable t) {\n\t\t\t\t\tSYNCHRONIZATION_LOGGER.synchronizationFailed( synchronization, t );\n\t\t\t\t\tthrow new LocalSynchronizationException(\n\t\t\t\t\t\t\t\"Exception calling user Synchronization (beforeCompletion): \" + synchronization.getClass().getName(),\n\t\t\t\t\t\t\tt\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void notifySynchronizationsAfterTransactionCompletion(int status) {\n\t\tSYNCHRONIZATION_LOGGER.notifyingSynchronizationsAfter( status );\n\t\tif ( synchronizations != null ) {\n\t\t\ttry {\n\t\t\t\tfor ( var synchronization : synchronizations ) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsynchronization.afterCompletion( status );\n\t\t\t\t\t}\n\t\t\t\t\tcatch (Throwable t) {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/resource/transaction/internal/SynchronizationRegistryStandardImpl.java#L42-L78","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\npublic class CleanupSync implements Synchronization {\n    public void beforeCompletion() {\n        auditMapper.insert(buildAuditRow()); // JDBC failure aborts commit\n    }\n    ...\n}\n\n// after\npublic void beforeCompletion() {\n    try {\n        auditMapper.insert(buildAuditRow());\n    }\n    catch (RuntimeException e) {\n        LOG.error(\"audit beforeCompletion failed\", e); // decide: swallow or rethrow\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    userTransaction.commit();\n}\ncatch (LocalSynchronizationException e) {\n    Throwable real = e.getCause(); // the actual beforeCompletion failure\n    // decide: rollback path, alerting, compensation\n}","preventionTips":["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"],"tags":["jta","synchronization","transactions","callbacks"],"backgroundTag":"synchronization-callback-failure","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}