{"record":{"id":"7fe3d7e862519ad0","repo":"hibernate/hibernate-orm","slug":"physical-transaction-delegate-is-no-longer-valid","errorCode":null,"errorMessage":"Physical-transaction delegate is no longer valid","messagePattern":"Physical-transaction delegate is no longer valid","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java","lineNumber":419,"sourceCode":"\n\t\tpublic TransactionDriverControlImpl(@Nonnull JtaTransactionAdapter jtaTransactionAdapter) {\n\t\t\tthis.jtaTransactionAdapter = jtaTransactionAdapter;\n\t\t}\n\n\t\tprotected void invalidate() {\n\t\t\tinvalid = true;\n\t\t}\n\n\t\t@Override\n\t\tpublic void begin() {\n\t\t\terrorIfInvalid();\n\t\t\tjtaTransactionAdapter.begin();\n\t\t\tjoinJtaTransaction();\n\t\t}\n\n\t\tprotected void errorIfInvalid() {\n\t\t\tif ( invalid ) {\n\t\t\t\tthrow new IllegalStateException( \"Physical-transaction delegate is no longer valid\" );\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void commit() {\n\t\t\terrorIfInvalid();\n\t\t\tgetTransactionCoordinatorOwner().flushBeforeTransactionCompletion();\n\t\t\t// we don't have to perform any before/after completion processing here.  We leave that for\n\t\t\t// the Synchronization callbacks\n\t\t\tjtaTransactionAdapter.commit();\n\t\t}\n\n\t\t@Override\n\t\tpublic void rollback() {\n\t\t\terrorIfInvalid();\n\t\t\t// we don't have to perform any after completion processing here.  We leave that for\n\t\t\t// the Synchronization callbacks\n\t\t\tjtaTransactionAdapter.rollback();","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java#L401-L437","documentation":"TransactionDriverControlImpl is the per-coordinator handle through which Hibernate's Transaction API drives the JTA adapter; once the coordinator invalidates it (invalidate() sets the flag after the JTA transaction cycle completed), any further begin()/commit()/rollback() throws this IllegalStateException. It means code is driving a Session/Transaction whose JTA transaction already finished - typically a cached Transaction reused after completion, a second commit, or begin() on a stale delegate.","triggerScenarios":"Calling begin(), commit() or rollback() on a Transaction whose delegate was invalidated: tx.commit() invoked twice; a Transaction or Session stored in a field/ThreadLocal and reused across requests; retry logic reusing the same transaction; extended-session patterns on JTA backends.","commonSituations":"Long-lived Sessions reused across container-managed transactions; cached Transaction objects; catch-and-retry loops that recommit; helpers that begin 'if not active' against a coordinator whose transaction already completed.","solutions":["Use a fresh Session (and its Transaction) per unit of work; never cache Transaction instances","Check Transaction.getStatus() before begin/commit and re-open the session when the delegate is stale","Make retry loops begin a new transaction rather than recommitting the old one","For extended contexts rely on container-managed patterns (OSIV / extended persistence context) instead of manual session reuse"],"exampleFix":"// before: transaction cached and reused after completion\nprivate final Transaction tx = session.getTransaction();\nvoid run() {\n    tx.commit();\n    tx.begin(); // IllegalStateException: delegate invalidated\n}\n\n// after: new session + transaction per unit of work\nvoid run() {\n    try (Session s = sessionFactory.openSession()) {\n        s.beginTransaction();\n        // ... work ...\n        s.getTransaction().commit();\n    }\n}","handlingStrategy":"validation","validationCode":"// Check before driving a possibly-stale Transaction\nTransactionStatus st = tx.getStatus();\nif ( st != TransactionStatus.NOT_ACTIVE && st != TransactionStatus.ACTIVE\n        && st != TransactionStatus.MARKED_ROLLBACK ) {\n    // delegate invalidated by a completed JTA tx: get a fresh session/transaction\n    session = sessionFactory.openSession();\n    tx = session.getTransaction();\n}","typeGuard":null,"tryCatchPattern":"try {\n    tx.begin();\n}\ncatch (IllegalStateException e) {\n    if ( e.getMessage() != null && e.getMessage().contains(\"no longer valid\") ) {\n        // stale delegate: open a new session and start a fresh transaction\n        try (Session fresh = sessionFactory.openSession()) {\n            fresh.beginTransaction();\n            // redo the unit of work\n        }\n    }\n    else {\n        throw e;\n    }\n}","preventionTips":["Open a new Session per unit of work; never cache Transaction objects in fields or ThreadLocals","Check Transaction.getStatus() before begin/commit","Make retry loops start a new transaction instead of reusing the old one","Close sessions in finally / use try-with-resources so delegates are never reused"],"tags":["jta","transactions","hibernate","session-lifecycle","illegal-state","reuse-after-completion"],"backgroundTag":"transaction-already-completed","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}