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
- Move @TransactionConfiguration(timeout) to the outermost/entry method that actually starts the transaction
- Remove the timeout annotation from inner joining methods (it has no effect there anyway)
- Change the inner method's @Transactional(TxType.REQUIRES_NEW) if it truly needs its own timeout — that makes it an entry point
- 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
- Put @TransactionConfiguration only on methods that START transactions
- Avoid @TransactionConfiguration on methods called from other @Transactional methods
- Use quarkus.transaction.default-transaction-timeout for global timeout needs
- Review nested @Transactional call graphs when adding timeout config
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Could not access JTA Transaction to register synchronization
- Error getting the status of the current transaction
- Error getting the current transaction
- Transaction was already rolled back (e.g., by the transactio
- Failed to load application configuration
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/9a76010b8aa02232.
Report an issue: GitHub.