quarkusio/quarkus · error · IllegalArgumentException
@ExactlyOnce on method ${methodName} cannot combine @Transac
Error message
@ExactlyOnce on method ${methodName} cannot combine @Transactional with @WithTransaction What it means
@Transactional (CDI interceptor) and @WithTransaction (reactive messaging annotation) are two mutually exclusive transaction mechanisms; applying both on an @ExactlyOnce method is ambiguous — the extension cannot decide which transaction manages the method. The build-time processor rejects the combination.
Source
Thrown at extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java:201
if (reactive && method.returnType().kind() == Type.Kind.PARAMETERIZED_TYPE) {
DotName typeArg = method.returnType().asParameterizedType().arguments().get(0).name();
if (typeArg.equals(VOID_BOXED)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " must return a value to produce to the outgoing channel");
}
}
if (method.hasAnnotation(DotNames.WITH_TRANSACTION) && !reactive) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " cannot combine @WithTransaction with a synchronous return type"
+ ", use @Transactional instead");
}
if (method.hasAnnotation(DotNames.TRANSACTIONAL) && method.hasAnnotation(DotNames.WITH_TRANSACTION)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " cannot combine @Transactional with @WithTransaction");
}
boolean isSuspend = method.parameterTypes().stream()
.anyMatch(t -> t.name().equals(DotNames.CONTINUATION));
if (isSuspend) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " does not support Kotlin suspend functions");
}
String incomingChannel = incoming.value().asString();
String outgoingChannel = outgoing.value().asString();
if (!discoveryState.isKafkaConnector(channelsManagedByConnectors, true, incomingChannel)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodNameView on GitHub (pinned to e1c734241f)
Solutions
- Keep only @Transactional for synchronous methods.
- Keep only @WithTransaction for reactive (Uni/Multi/CompletionStage) methods.
- Remove the redundant annotation depending on the intended execution model.
Example fix
// before
@ExactlyOnce @Transactional @WithTransaction
@Incoming("in") @Outgoing("out")
public Uni<String> process(String p) { ... }
// after
@ExactlyOnce @WithTransaction
@Incoming("in") @Outgoing("out")
public Uni<String> process(String p) { ... } Defensive patterns
Strategy: validation
Validate before calling
// reject double transaction annotations
if (method.isAnnotationPresent(Transactional.class) && method.isAnnotationPresent(WithTransaction.class)) {
throw new IllegalStateException("Choose either @Transactional or @WithTransaction");
} Prevention
- Audit methods for stacked transaction annotations before adding @ExactlyOnce
- Document which transaction annotation is the codebase standard
When it happens
Trigger: A single @ExactlyOnce method carries both @jakarta.transaction.Transactional and @WithTransaction annotations simultaneously.
Common situations: Adding @WithTransaction to a method that already had @Transactional (or vice versa) during migration between sync and reactive styles.
Related errors
- @ExactlyOnce on method ${methodName} cannot combine @WithTra
- @ExactlyOnce on method ${methodName} requires both @Incoming
- @ExactlyOnce on method ${methodName} must return a value to
- @ExactlyOnce on method ${methodName} cannot be combined with
- @ExactlyOnce on method ${methodName} does not support Kotlin
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/68268d712556ebb5.
Report an issue: GitHub.