quarkusio/quarkus · error · RuntimeException

Transaction rolled back due to:

Error message

Transaction rolled back due to: 

What it means

When an exception escapes a reactive @Transactional method, the interceptor rolls back the transaction and rethrows a RuntimeException("Transaction rolled back due to: ", exception) to force the reactive chain to fail. It signals that the DB transaction was rolled back because of the wrapped cause.

Solutions

  1. Inspect the wrapped cause (getCause()) to fix the actual DB failure, e.g. duplicate keys or constraint violations.
  2. Add @ReactiveTransactional only where needed and handle expected failures in the Uni chain with onFailure/recoverWithItem.
  3. If commits fail systematically, verify datasource connectivity and transaction configuration (quarkus.datasource.*, quarkus.hibernate-orm.*).

Example fix

// before
client.query(...).chain(unchecked(this::save)) // exception bubbles as rolled-back RuntimeException
// after
return client.query(...).chain(this::save)
    .onFailure(DuplicateKeyException.class, f -> log.warn("dup"))
    .onItem().transform(x -> Response.ok(x).build());
Defensive patterns

Strategy: try-catch

Try / catch

try {
    return service.save(entity).await().indefinitely();
} catch (RuntimeException e) {
    Throwable cause = e.getCause();
    log.error("tx rolled back: {}", cause != null ? cause.getMessage() : e.getMessage());
    throw cause instanceof RuntimeException ? (RuntimeException) cause : e;
}

Prevention

When it happens

Trigger: A reactive @Transactional method's Uni chain fails (e.g. constraint violation, query error), or rollback is triggered by rollbackOrCommitBasedOnException after commit preparation fails.

Common situations: Unique/constraint violations on persist; serialization failures; a downstream service call inside a transaction throwing; misconfigured datasource causing commit/rollback failures.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/df373846ebec67c9. Report an issue: GitHub.

Appendix: source

Thrown at extensions/reactive-transactions/runtime/src/main/java/io/quarkus/reactive/transaction/runtime/TransactionalInterceptorBase.java:123

    }

    private Uni<Void> invokeBeforeCommitAndCommit(Context context) {
        return reactiveResource.beforeCommit(context)
                .onItem().invoke(() -> LOG.tracef("Flushed the session before commit/rollback"))
                .onItemOrFailure().call((result, exception) -> {
                    if (exception != null) {
                        Uni<SqlConnection> connectionUni = connectionFromContext();
                        if (connectionUni == null) {
                            LOG.tracef("Transaction doesn't exist, cannot rollback, propagating original exception");
                            return Uni.createFrom().failure(
                                    new RuntimeException("Transaction rolled back due to: ", exception));
                        }
                        return connectionUni
                                .onItem()
                                .transformToUni(connection -> actualRollback(connection.transaction(), exception).invoke(() -> {
                                    // onItemOrFailure() will still propagate the chain even with an execption
                                    // we need to rethrow it to make sure the reactive chain fails
                                    throw new RuntimeException("Transaction rolled back due to: ", exception);
                                }));
                    } else {
                        return commit();
                    }
                }).replaceWithVoid();
    }

    private Uni<?> closeConnection() {
        Future<Void> closeFuture = TransactionalContextPool.closeAndClearCurrentConnection();
        if (closeFuture == null) {
            // io/quarkus/hibernate/reactive/transaction/DisableJTATransactionTest.java:38
            LOG.tracef("Connection doesn't exist, nothing to do here");
            return Uni.createFrom().nullItem();
        }
        return toUni(closeFuture)
                .invoke(connection -> LOG.tracef("Closing the connection %s", connection));
    }

View on GitHub (pinned to e1c734241f)