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

  1. Remove @Blocking from the method.
  2. Move blocking work inside the method body wrapped in Mutiny's runSubscription/emitOn if truly needed, keeping the method itself non-blocking.
  3. 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

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


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