quarkusio/quarkus · error · IllegalStateException
Syncs are not allowed because the group of synchronizations
Error message
Syncs are not allowed because the group of synchronizations to which this sync belongs has already ran
What it means
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.
Source
Thrown at extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/internal/tsr/AgroalOrderedLastSynchronizationList.java:60
*
* The beforeCompletion methods within a group are called in the order they were added,
* and the afterCompletion methods are ran in the reverse order
*/
private class SynchronizationGroup implements Synchronization {
String packagePrefix; // Synchronizations with this package prefix belong to this group
final List<Synchronization> synchs; // the Synchronizations in the group
volatile ExecutionStatus status; // track the status to decide when it's too late to allow more registrations
public SynchronizationGroup(String packagePrefix) {
this.packagePrefix = packagePrefix;
this.synchs = new ArrayList<>();
this.status = ExecutionStatus.PENDING;
}
public void add(Synchronization synchronization) {
if (status == ExecutionStatus.FINISHED) {
// this group of syncs have already ran
throw new IllegalStateException(ADD_SYNC_ERROR);
}
synchs.add(synchronization);
}
@Override
public void beforeCompletion() {
status = ExecutionStatus.RUNNING;
// Note that because synchronizations can register other synchronizations
// we cannot use enhanced for loops as that could cause a concurrency exception
for (int i = 0; i < synchs.size(); i++) {
Synchronization sync = synchs.get(i);
try {
sync.beforeCompletion();
} catch (Exception e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf(View on GitHub (pinned to e1c734241f)
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)
Example fix
// before: registering a sync inside beforeCompletion
public void beforeCompletion() { list.add(mySync); } // FINISHED group -> throws
// after: register at TX start, not during completion
txManager.getTransaction().registerSynchronization(mySync); // early Defensive patterns
Strategy: validation
Validate before calling
// register synchronizations only while TX is active
if (transactionManager.getStatus() != jakarta.transaction.Status.STATUS_ACTIVE) {
throw new IllegalStateException("Too late to enlist synchronization");
} Try / catch
try { registerLateSync(); } catch (IllegalStateException e) {
if (e.getMessage().contains("Syncs are not allowed")) { log.warn("Late sync skipped", e); }
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Cannot register synchronization
- Syncs are not allowed to be registered when the transaction
- Error getting the status of the current transaction
- Error getting the current transaction
- Transaction was already rolled back (e.g., by the transactio
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/5403c379bd43fac1.
Report an issue: GitHub.