quarkusio/quarkus · error · UnsupportedOperationException

@Transactional on Reactive methods supports only Transaction

Error message

@Transactional on Reactive methods supports only Transactional.TxType.REQUIRED

What it means

The base reactive transactional interceptor only supports TxType.REQUIRED for @Transactional-annotated reactive methods; other tx types (REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER) have no reactive implementation and throw UnsupportedOperationException. Subclasses (e.g. the JTA reactive interceptor) may override this check.

Source

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

     * @return the current vertx duplicated context
     * @throws IllegalStateException If no vertx context is found or is not a safe context as mandated by the
     *         {@link VertxContextSafetyToggle}
     */
    private static Context vertxContext() {
        Context context = Vertx.currentContext();
        if (context != null) {
            VertxContextSafetyToggle.validateContextIfExists(ERROR_MSG, ERROR_MSG);
            return context;
        } else {
            throw new IllegalStateException("No current Vertx context found");
        }
    }

    // Default impl fails .REQUIRED overrides it
    protected void validateTransactionalType(InvocationContext context) {
        Transactional transactional = context.getMethod().getAnnotation(Transactional.class);
        if (transactional != null && transactional.value() != Transactional.TxType.REQUIRED) {
            throw new UnsupportedOperationException(
                    "@Transactional on Reactive methods supports only Transactional.TxType.REQUIRED");
        }
    }

    /**
     * <p>
     * Looking for the {@link Transactional} annotation first on the method,
     * second on the class.
     * <p>
     * Method handles CDI types to cover cases where extensions are used. In
     * case of EE container uses reflection.
     *
     * @param context invocation context of the interceptor
     * @return instance of {@link Transactional} annotation or null
     */
    private Transactional getTransactionalAnnotation(InvocationContext context) {
        Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(context);
        for (Annotation binding : bindings) {

View on GitHub (pinned to e1c734241f)

Solutions

  1. Use @Transactional (default REQUIRED) on the reactive method.
  2. If an independent new transaction is truly needed, split the operation into a separate bean method and call it so the outer transaction isn't active, or use @WithTransaction on the sub-operation.
  3. If using the Narayana STJ integration, use @ReactiveTransactional which supports the required semantics.

Example fix

// before
@Transactional(Transaction.TxType.REQUIRES_NEW)
public Uni<Void> audit(Event e) { ... }
// after
@Transactional
public Uni<Void> audit(Event e) { ... }
Defensive patterns

Strategy: validation

Validate before calling

Transactional t = method.getAnnotation(Transactional.class);
if (t != null && t.value() != Transactional.TxType.REQUIRED)
    throw new IllegalArgumentException("Reactive @Transactional supports only REQUIRED, found " + t.value());

Try / catch

try {
    return service.audit(e);
} catch (UnsupportedOperationException ex) {
    log.error("Use TxType.REQUIRED on reactive methods: {}", ex.getMessage());
    throw ex;
}

Prevention

When it happens

Trigger: Annotating a Uni-returning reactive method with @Transactional(value = Transactional.TxType.REQUIRES_NEW) (or any type other than REQUIRED) and having the base interceptor validate it.

Common situations: Copy-pasting blocking JTA tx types into reactive services; trying REQUIRES_NEW for nested independent transactions in reactive code.

Related errors


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