quarkusio/quarkus · error · RuntimeException

Changing timeout via @TransactionConfiguration can only be d

Error message

Changing timeout via @TransactionConfiguration can only be done at the entry level of a transaction

What it means

@TransactionConfiguration(timeout=...) sets the JTA transaction timeout, which is only meaningful when the transaction is first started. If the interceptor sees this annotation on a method that merely joins an existing transaction (invoked in caller's TX or in an ongoing TX), it throws this RuntimeException because the timeout would silently be ignored. The check runs in checkConfiguration, invoked from invokeInCallerTx and invokeInNoTx.

Source

Thrown at extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java:383

            checkConfiguration(ic);
            return ic.proceed();
        } catch (Throwable t) {
            handleException(ic, t, tx);
        }
        throw new RuntimeException("UNREACHABLE");
    }

    protected Object invokeInNoTx(InvocationContext ic) throws Exception {
        checkConfiguration(ic);
        return ic.proceed();
    }

    private void checkConfiguration(InvocationContext ic) {
        TransactionConfiguration configAnnotation = getTransactionConfiguration(ic);
        if (configAnnotation != null && ((configAnnotation.timeout() != TransactionConfiguration.UNSET_TIMEOUT)
                || !TransactionConfiguration.UNSET_TIMEOUT_CONFIG_PROPERTY
                        .equals(configAnnotation.timeoutFromConfigProperty()))) {
            throw new RuntimeException("Changing timeout via @TransactionConfiguration can only be done " +
                    "at the entry level of a transaction");
        }
    }

    protected void handleExceptionNoThrow(InvocationContext ic, Throwable t, Transaction tx)
            throws IllegalStateException, SystemException {
        Transactional transactional = getTransactional(ic);

        for (Class<?> dontRollbackOnClass : transactional.dontRollbackOn()) {
            if (dontRollbackOnClass.isAssignableFrom(t.getClass())) {
                return;
            }
        }

        for (Class<?> rollbackOnClass : transactional.rollbackOn()) {
            if (rollbackOnClass.isAssignableFrom(t.getClass())) {
                safeSetRollbackOnly(tx);
                return;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Move @TransactionConfiguration(timeout) to the outermost/entry method that actually starts the transaction
  2. Remove the timeout annotation from inner joining methods (it has no effect there anyway)
  3. Change the inner method's @Transactional(TxType.REQUIRES_NEW) if it truly needs its own timeout — that makes it an entry point
  4. Set the default timeout globally via quarkus.transaction.default-transaction-timeout if all transactions need more time

Example fix

// before
@Transactional @TransactionConfiguration(timeout = 120)
public void inner() { ... }
// after: put config on entry point
@Transactional @TransactionConfiguration(timeout = 120)
public void outer() { inner(); }
@Transactional
public void inner() { ... }
Defensive patterns

Strategy: validation

Validate before calling

// only annotate the entry method that starts the TX
boolean isEntryMethod = transactionManager.getStatus() == jakarta.transaction.Status.STATUS_NO_TRANSACTION;
// apply @TransactionConfiguration only when isEntryMethod

Prevention

When it happens

Trigger: Nested call chain: outer @Transactional method starts the TX, an inner @Transactional method also declares @TransactionConfiguration(timeout=X or timeoutFromConfigProperty) and is invoked while a transaction is already active (REQUIRED joins the existing TX).

Common situations: Developers adding @TransactionConfiguration to a helper/service method assuming it applies per-method, not realizing an outer method already opened the transaction; copy-pasting the timeout annotation across a call graph; changing MANDATORY methods to add timeouts.

Understand the failure class

Related errors


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