quarkusio/quarkus · error · IllegalArgumentException
@ExactlyOnce on method ${methodName} cannot be combined with
Error message
@ExactlyOnce on method ${methodName} cannot be combined with @Blocking What it means
@ExactlyOnce methods execute inside a Kafka transaction managed reactively by the extension; @Blocking would run the method on a worker thread and break the transactional/vert.x context the exactly-once invoker relies on. The deployment-time processor rejects the combination outright.
Source
Thrown at extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java:175
}
for (Type paramType : method.parameterTypes()) {
if (paramType.name().equals(DotNames.MESSAGE)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " does not support Message parameters, use payload types instead");
}
}
if (method.returnType().name().equals(DotNames.VOID)
|| method.returnType().name().equals(VOID_BOXED)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " must return a value to produce to the outgoing channel");
}
if (method.hasAnnotation(DotNames.BLOCKING) || method.hasAnnotation(DotNames.SMALLRYE_BLOCKING)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " cannot be combined with @Blocking");
}
DotName returnTypeName = method.returnType().name();
boolean reactive = DotNames.UNI.equals(returnTypeName) || DotNames.MULTI.equals(returnTypeName)
|| DotNames.COMPLETION_STAGE.equals(returnTypeName);
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) {View on GitHub (pinned to e1c734241f)
Solutions
- Remove @Blocking from the method.
- Move blocking work inside the method body wrapped in Mutiny's runSubscription/emitOn if truly needed, keeping the method itself non-blocking.
- Keep the return type reactive (Uni/Multi/CompletionStage) and avoid blocking annotations.
Example fix
// before
@ExactlyOnce @Blocking
@Incoming("in") @Outgoing("out")
public String process(String p) { ... }
// after
@ExactlyOnce
@Incoming("in") @Outgoing("out")
public String process(String p) { ... } Defensive patterns
Strategy: validation
Validate before calling
// before build: assert no @Blocking on @ExactlyOnce methods
if (method.isAnnotationPresent(Blocking.class) || method.isAnnotationPresent(io.smallrye.common.annotation.Blocking.class)) {
throw new IllegalStateException("@Blocking conflicts with @ExactlyOnce");
} Prevention
- Keep @ExactlyOnce methods non-blocking with reactive return types
- Move blocking work inside the method body, not via @Blocking
- Grep your codebase for @Blocking near @ExactlyOnce before upgrading
When it happens
Trigger: A method annotated with both @ExactlyOnce and @Blocking (or io.smallrye.common.annotation.Blocking) is discovered during build.
Common situations: Adding @Blocking to an @ExactlyOnce processor to do JDBC or other blocking work, or inheriting @Blocking from a base class while enabling exactly-once.
Related errors
- @ExactlyOnce on method ${methodName} requires both @Incoming
- @ExactlyOnce on method ${methodName} must return a value to
- @ExactlyOnce on method ${methodName} cannot combine @WithTra
- @ExactlyOnce on method ${methodName} cannot combine @Transac
- @ExactlyOnce on method ${methodName} does not support Kotlin
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/c73185ec3d908845.
Report an issue: GitHub.