{"record":{"id":"d9cdc9b8bb5c1cd0","repo":"quarkusio/quarkus","slug":"syncs-are-not-allowed-to-be-registered-when-the-tr","errorCode":null,"errorMessage":"Syncs are not allowed to be registered when the transaction is in state ","messagePattern":"Syncs are not allowed to be registered when the transaction is in state ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/internal/tsr/AgroalOrderedLastSynchronizationList.java","lineNumber":143,"sourceCode":"     * <p>\n     *\n     * @param synchronization The synchronization to register\n     * @throws IllegalStateException if the transaction is in the wrong state:\n     *         <ol>\n     *         <li>the transaction has already prepared;\n     *         <li>the transaction is marked rollback only\n     *         <li>the group that the synchronization should belong to has already been processed\n     *         </ol>\n     */\n    public void registerInterposedSynchronization(Synchronization synchronization) {\n        int status = tsr.getTransactionStatus();\n\n        switch (status) {\n            case Status.STATUS_ACTIVE:\n            case Status.STATUS_PREPARING:\n                break;\n            default:\n                throw new IllegalStateException(REGISTER_SYNC_ERROR + status);\n        }\n\n        // add the synchronization to the group that matches this package and, if there is no such group\n        // then add it to the catch-all group (otherSyncs)\n        String packageName = synchronization.getClass().getName();\n        SynchronizationGroup synchGroup = otherSynchs;\n\n        for (SynchronizationGroup g : synchGroups) {\n            if (g.shouldAdd(packageName)) {\n                synchGroup = g;\n                break;\n            }\n        }\n\n        synchGroup.add(synchronization);\n    }\n\n    /**","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/internal/tsr/AgroalOrderedLastSynchronizationList.java#L125-L161","documentation":"Quarkus's TSR registerInterposedSynchronization only accepts new interposed synchronizations while the transaction is STATUS_ACTIVE or STATUS_PREPARING. Any other state (e.g. PREPARED, COMMITTING, ROLLED_BACK, ROLLEDBACK, COMMITTED) is too late, and the code throws this IllegalStateException, appending the offending status to the message. It prevents silent loss of cleanup callbacks that would never run.","triggerScenarios":"Calling registerInterposedSynchronization(synchronization) when tx.getStatus() is not ACTIVE or PREPARING — e.g. registering during commit/rollback completion, or after the transaction already terminated.","commonSituations":"Connection close/cleanup handlers running after the TX decided its fate; resource enlistment triggered lazily during commit; asynchronous tasks completing and registering syncs after the TX ended; reaper-rolled-back transactions still being enlisted by slow code.","solutions":["Register interposed synchronizations while the transaction is active, earlier in the business flow","Check transaction status before registering and skip/degrade gracefully if the TX is terminating","Avoid acquiring new TX-enlisted resources inside completion callbacks or after commit/rollback started","If triggered by a driver/extension, ensure resources are closed before the transaction completes (e.g. proper request-scope cleanup)"],"exampleFix":"// before\nint status = tx.getStatus();\nif (status != Status.STATUS_ACTIVE) { /* still registers -> throws */ }\nregisterInterposedSynchronization(sync);\n// after\nint status = tx.getStatus();\nif (status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING) {\n    registerInterposedSynchronization(sync);\n}","handlingStrategy":"validation","validationCode":"int st = transactionManager.getStatus();\nboolean registrationAllowed =\n    st == jakarta.transaction.Status.STATUS_ACTIVE || st == jakarta.transaction.Status.STATUS_PREPARING;","typeGuard":null,"tryCatchPattern":"try { registerInterposed(sync); } catch (IllegalStateException e) {\n    if (e.getMessage().startsWith(\"Syncs are not allowed to be registered\")) { /* run cleanup directly instead */ }\n}","preventionTips":["Check tx status before registering interposed synchronizations","Complete all resource enlistment while the TX is ACTIVE","Don't acquire TX-enlisted resources in async tasks racing the commit","Close resources before commit/rollback starts"],"tags":["jta","synchronization","transaction-state","agroal","narayana"],"backgroundTag":"late-synchronization-registration","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}