quarkusio/quarkus · error · UnsupportedOperationException
Calling a method annotated with @Transactional from a method
Error message
Calling a method annotated with @Transactional from a method annotated with @WithSessionOnDemand is not supported. Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, but not both, throughout your whole application.
What it means
The reactive @Transactional interceptor detects a leftover @WithSessionOnDemand marker in the Vertx context locals, meaning a legacy Panache session annotation is active up the call stack. Mixing legacy Panache session annotations with plain @Transactional is unsupported, so it throws UnsupportedOperationException.
Source
Thrown at extensions/reactive-transactions/runtime/src/main/java/io/quarkus/reactive/transaction/runtime/TransactionalInterceptorBase.java:272
"For reactive methods running on the event loop, @Transactional can only be used if the method returns a `Uni`. Found '"
+ result.getClass().getName() + "' instead.");
}
} catch (Exception e) {
return Uni.createFrom().failure(e);
}
}
public static boolean reactiveInterceptorShouldRun() {
boolean condition = Context.isOnEventLoopThread();
LOG.tracef("Transactional interceptor should run: %s", condition);
return condition;
}
protected void validateLegacyPanacheAnnotations() {
// We are running on the retrieved context, however, the method also switch the safety flag.
Context ignored = vertxContext();
if (ContextLocals.get(SESSION_ON_DEMAND_KEY).isPresent()) {
throw new UnsupportedOperationException(
"Calling a method annotated with @Transactional from a method annotated with @WithSessionOnDemand is not supported. "
+ "Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, "
+ "but not both, throughout your whole application.");
}
if (ContextLocals.get(WITH_TRANSACTION_METHOD_KEY).isPresent()) {
throw new UnsupportedOperationException(
"Calling a method annotated with @Transactional from a method annotated with @WithTransaction is not supported. "
+ "Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, "
+ "but not both, throughout your whole application.");
}
if (ContextLocals.get(REACTIVE_TRANSACTIONAL_METHOD_KEY).isPresent()) {
throw new UnsupportedOperationException(
"Calling a method annotated with @Transactional from a method annotated with @ReactiveTransactional is not supported. "
+ "Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, "
+ "but not both, throughout your whole application.");
}View on GitHub (pinned to e1c734241f)
Solutions
- Remove @WithSessionOnDemand from the calling method and rely on @Transactional (or vice versa).
- Standardize the whole application on @Transactional/@ReactiveTransactional for transactional reactive methods.
- Grep the codebase for @WithSessionOnDemand/@WithSession/@WithTransaction and remove overlaps with @Transactional.
Example fix
// before
@WithSessionOnDemand
public Uni<Item> load() { return service.find(); }
@Transactional public Uni<Item> find() { ... }
// after
@Transactional
public Uni<Item> load() { return service.find(); }
@Transactional
public Uni<Item> find() { ... } Defensive patterns
Strategy: validation
Validate before calling
// detect mixed transactional annotations before runtime
void checkAnnotations(Class<?> bean) {
for (Method m : bean.getDeclaredMethods()) {
boolean legacy = m.isAnnotationPresent(WithSessionOnDemand.class);
boolean tx = m.isAnnotationPresent(Transactional.class);
if (legacy && tx) throw new IllegalStateException("Mixed @WithSessionOnDemand + @Transactional in " + bean);
}
} Try / catch
try {
return txService.find(id);
} catch (UnsupportedOperationException e) {
log.error("Incompatible tx annotations in call chain: {}", e.getMessage());
throw e;
} Prevention
- Pick one style (Transactional/@ReactiveTransactional) per application
- Grep for @WithSession/@WithSessionOnDemand/@WithTransaction during migration
- Write ArchUnit tests banning legacy Panache annotations alongside @Transactional
- Review call graphs, not just individual methods, when migrating
When it happens
Trigger: Calling a method annotated @Transactional from a method annotated @WithSessionOnDemand (the flag SESSION_ON_DEMAND_KEY is present in the context).
Common situations: Old Panache code using @WithSession/@WithSessionOnDemand gradually migrated to @Transactional, leaving mixed annotations in one call chain.
Related errors
- Calling a method annotated with @Transactional from a method
- Calling a method annotated with @Transactional from a method
- Unable to load repository/entity class mapping ->
- Your repository class was not properly detected and assigne
- Entity '%s' was not found. Did you forget to annotate your P
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/a76a9ee12d8c9171.
Report an issue: GitHub.