{"record":{"id":"743a6e67d810092e","repo":"hibernate/hibernate-orm","slug":"unable-to-register-cleanup-synchronization-with-tr","errorCode":null,"errorMessage":"Unable to register cleanup Synchronization with TransactionManager","messagePattern":"Unable to register cleanup Synchronization with TransactionManager","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java","lineNumber":94,"sourceCode":"\t\telse {\n\t\t\tvalidateExistingSession( currentSession );\n\t\t}\n\n\t\treturn currentSession;\n\t}\n\n\tprivate void registerSynchronization(Transaction txn, Object txnIdentifier, Session currentSession) {\n\t\ttry {\n\t\t\ttxn.registerSynchronization( buildCleanupSynch( txnIdentifier ) );\n\t\t}\n\t\tcatch ( Throwable t ) {\n\t\t\ttry {\n\t\t\t\tcurrentSession.close();\n\t\t\t}\n\t\t\tcatch ( Throwable e ) {\n\t\t\t\tCURRENT_SESSION_LOGGER.unableToReleaseGeneratedCurrentSessionOnFailedSynchronizationRegistration(e);\n\t\t\t}\n\t\t\tthrow new HibernateException( \"Unable to register cleanup Synchronization with TransactionManager\" );\n\t\t}\n\t}\n\n\tprivate static @Nonnull Transaction getTransaction(TransactionManager transactionManager) {\n\t\ttry {\n\t\t\tfinal var transaction = transactionManager.getTransaction();\n\t\t\tif ( transaction == null ) {\n\t\t\t\tthrow new HibernateException( \"Unable to locate current JTA transaction\" );\n\t\t\t}\n\t\t\tif ( !isActive( transaction.getStatus() ) ) {\n\t\t\t\t// We could register the session against the transaction even though it is\n\t\t\t\t// not started, but we'd have no guarantee of ever getting the map\n\t\t\t\t// entries cleaned up (aside from spawning threads).\n\t\t\t\tthrow new HibernateException( \"Current transaction is not in progress\" );\n\t\t\t}\n\t\t\treturn transaction;\n\t\t}\n\t\tcatch ( HibernateException e ) {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java#L76-L112","documentation":"After building a session for the current JTA transaction, JTASessionContext registers a Synchronization on that transaction so the session is removed from the map when the transaction completes. If Transaction.registerSynchronization() throws — transaction already completed, marked rollback-only, or TM-specific restrictions — Hibernate closes the freshly built session and throws this HibernateException.","triggerScenarios":"Calling getCurrentSession() when the JTA transaction is in a state that no longer accepts synchronizations: after it was marked rollback-only by an earlier failure, while it is preparing/completing, from afterCompletion-style callbacks, or when another component's synchronization registration poisoned the transaction.","commonSituations":"First DB access happening in the tail of a dying transaction (@PreDestroy, afterCompletion hooks, async handoff from a request thread); a previous exception marked the TX rollback-only but processing continued; transaction timeouts racing session access.","solutions":["Move current-session access earlier, well before the transaction can complete","Check the transaction status is STATUS_ACTIVE before calling getCurrentSession() and skip or fast-fail otherwise","Find the earlier failure that marked the transaction rollback-only — that exception is the root cause, not this one","For work that must run after completion, open a plain sessionFactory.openSession() with its own short transaction"],"exampleFix":"// before\npublic void afterCompletion() {\n    sessionFactory.getCurrentSession().persist(audit); // dying tx -> sync registration fails\n}\n\n// after\npublic void afterCompletion() {\n    try (Session s = sessionFactory.openSession()) {\n        s.beginTransaction();\n        s.persist(audit);\n        s.getTransaction().commit();\n    }\n}","handlingStrategy":"try-catch","validationCode":"TransactionManager tm = platform.retrieveTransactionManager();\nif (tm.getStatus() == jakarta.transaction.Status.STATUS_ACTIVE) {\n    Session s = sessionFactory.getCurrentSession(); // safe: sync registration will succeed\n}","typeGuard":null,"tryCatchPattern":"try {\n    return sessionFactory.getCurrentSession();\n} catch (org.hibernate.HibernateException e) {\n    if (e.getMessage().contains(\"register cleanup Synchronization\")) {\n        // session was already closed by Hibernate; run the work in a fresh transaction instead of retrying\n        return runInNewTransaction(this::doWork);\n    }\n    throw e;\n}","preventionTips":["Access current sessions only while the transaction is STATUS_ACTIVE","Never call getCurrentSession() from completion callbacks or @PreDestroy","Investigate rollback-only marks as root causes, not as noise to retry through"],"tags":["hibernate","jta","synchronization","transaction-lifecycle","current-session"],"backgroundTag":"jta-synchronization-registration-failed","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}