{"record":{"id":"3a702613596a3274","repo":"hibernate/hibernate-orm","slug":"exception-calling-user-synchronization-aftercompl","errorCode":null,"errorMessage":"Exception calling user Synchronization (afterCompletion): ${synchronization.getClass().getName()}","messagePattern":"Exception calling user Synchronization \\(afterCompletion\\): (.+?)","errorType":"exception","errorClass":"LocalSynchronizationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/resource/transaction/internal/SynchronizationRegistryStandardImpl.java","lineNumber":80,"sourceCode":"\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) {\n\t\t\t\t\t\tSYNCHRONIZATION_LOGGER.synchronizationFailed( synchronization, t );\n\t\t\t\t\t\tthrow new LocalSynchronizationException(\n\t\t\t\t\t\t\t\t\"Exception calling user Synchronization (afterCompletion): \" + synchronization.getClass().getName(),\n\t\t\t\t\t\t\t\tt\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinally {\n\t\t\t\tclearSynchronizations();\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void clearSynchronizations() {\n\t\tSYNCHRONIZATION_LOGGER.clearingSynchronizations();\n\t\tif ( synchronizations != null ) {\n\t\t\tsynchronizations.clear();\n\t\t}","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/resource/transaction/internal/SynchronizationRegistryStandardImpl.java#L62-L98","documentation":"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.","triggerScenarios":"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.","commonSituations":"Cleanup callbacks releasing connections or message listeners; code assuming status == STATUS_COMMITTED and failing on rollback; background cleanup interacting with an already-closed Session.","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"],"exampleFix":"// before\npublic void afterCompletion(int status) {\n    jmsSession.close(); // throws JMSException -> aborts notification loop\n}\n\n// after\npublic void afterCompletion(int status) {\n    try {\n        jmsSession.close();\n    }\n    catch (Exception e) {\n        LOG.warn(\"failed to close JMS session after tx completion (status={})\", status, e);\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    userTransaction.commit();\n}\ncatch (LocalSynchronizationException e) {\n    // afterCompletion failed; check tx status to learn the real outcome\n    int status = userTransaction.getStatus();\n    LOG.error(\"afterCompletion callback failed; tx status={}\", status, e.getCause());\n}","preventionTips":["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"],"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"}