{"record":{"id":"5403c379bd43fac1","repo":"quarkusio/quarkus","slug":"syncs-are-not-allowed-because-the-group-of-synchro","errorCode":null,"errorMessage":"Syncs are not allowed because the group of synchronizations to which this sync belongs has already ran","messagePattern":"Syncs are not allowed because the group of synchronizations to which this sync belongs has already ran","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":60,"sourceCode":"     *\n     * The beforeCompletion methods within a group are called in the order they were added,\n     * and the afterCompletion methods are ran in the reverse order\n     */\n    private class SynchronizationGroup implements Synchronization {\n        String packagePrefix; // Synchronizations with this package prefix belong to this group\n        final List<Synchronization> synchs; // the Synchronizations in the group\n        volatile ExecutionStatus status; // track the status to decide when it's too late to allow more registrations\n\n        public SynchronizationGroup(String packagePrefix) {\n            this.packagePrefix = packagePrefix;\n            this.synchs = new ArrayList<>();\n            this.status = ExecutionStatus.PENDING;\n        }\n\n        public void add(Synchronization synchronization) {\n            if (status == ExecutionStatus.FINISHED) {\n                // this group of syncs have already ran\n                throw new IllegalStateException(ADD_SYNC_ERROR);\n            }\n            synchs.add(synchronization);\n        }\n\n        @Override\n        public void beforeCompletion() {\n            status = ExecutionStatus.RUNNING;\n\n            // Note that because synchronizations can register other synchronizations\n            // we cannot use enhanced for loops as that could cause a concurrency exception\n            for (int i = 0; i < synchs.size(); i++) {\n                Synchronization sync = synchs.get(i);\n\n                try {\n                    sync.beforeCompletion();\n                } catch (Exception e) {\n                    if (LOGGER.isDebugEnabled()) {\n                        LOGGER.debugf(","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/internal/tsr/AgroalOrderedLastSynchronizationList.java#L42-L78","documentation":"Quarkus's AgroalOrderedLastSynchronizationList registers JTA synchronizations in ordered groups; each group tracks whether it already ran (FINISHED) during the two-phase completion. Adding a synchronization to a group that already executed is illegal, so add() throws this IllegalStateException. This happens when a datasource/connection proxy tries to register its cleanup sync after beforeCompletion/afterCompletion has started.","triggerScenarios":"Calling add(synchronization) on a SynchronizationList whose status == FINISHED — i.e. registerInterposedSynchronization is invoked for a group that already ran during transaction completion (between beforeCompletion and afterCompletion, or after completion).","commonSituations":"Acquiring a new DB connection from a resource already involved in the TX during the completion phase; lazy resources registering interceptors late; code in beforeCompletion opening new enlisted resources; custom Synchronization registration triggered from within another synchronization callback.","solutions":["Ensure all resources are enlisted before the transaction reaches the completing state — don't open new connections during beforeCompletion","Move registration earlier in the transactional work (before the last business call)","Audit custom Synchronization/InterposedSynchronization code that registers other syncs from completion callbacks","Upgrade Quarkus if you suspect a resource-late-enlistment bug in an extension (known issues fixed over time)"],"exampleFix":"// before: registering a sync inside beforeCompletion\npublic void beforeCompletion() { list.add(mySync); } // FINISHED group -> throws\n// after: register at TX start, not during completion\ntxManager.getTransaction().registerSynchronization(mySync); // early","handlingStrategy":"validation","validationCode":"// register synchronizations only while TX is active\nif (transactionManager.getStatus() != jakarta.transaction.Status.STATUS_ACTIVE) {\n    throw new IllegalStateException(\"Too late to enlist synchronization\");\n}","typeGuard":null,"tryCatchPattern":"try { registerLateSync(); } catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"Syncs are not allowed\")) { log.warn(\"Late sync skipped\", e); }\n}","preventionTips":["Enlist all resources and syncs before the final business call","Never open new DB connections inside beforeCompletion/afterCompletion callbacks","Avoid registering syncs from within other synchronizations","Keep extension versions in sync to avoid late-enlistment bugs"],"tags":["jta","synchronization","transaction","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"}