quarkusio/quarkus · error · IllegalArgumentException

@ExactlyOnce on method ${methodName} must return a value to

Error message

@ExactlyOnce on method ${methodName} must return a value to produce to the outgoing channel

What it means

This build-time validation in the Quarkus Kafka extension fires when a method annotated with @ExactlyOnce declares a void (or boxed Void) return type. @ExactlyOnce processing works by writing the produced value to the outgoing Kafka channel inside the same Kafka transaction as the incoming record offset commit, so the method must actually return a value to produce. A void-returning method cannot participate in the exactly-once transactional pipeline, so the deployment fails with this error.

Source

Thrown at extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java:169

                        "@ExactlyOnce on method " + methodName + " requires both @Incoming and @Outgoing annotations");
            }

            if (method.parametersCount() == 0) {
                throw new IllegalArgumentException(
                        "@ExactlyOnce on method " + methodName + " requires at least one parameter");
            }

            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(

View on GitHub (pinned to e1c734241f)

Solutions

  1. Change the method to return the payload (or Message) that should be sent to the outgoing channel.
  2. If the method does not need to produce anything, remove @ExactlyOnce and use plain @Transactional / manual acknowledgement instead.
  3. Switch the outgoing channel semantics: if you only need at-least-once, drop @ExactlyOnce.

Example fix

// before
@Incoming("in")
@Outgoing("out")
@ExactlyOnce
public void process(String payload) { ... }

// after
@Incoming("in")
@Outgoing("out")
@ExactlyOnce
public String process(String payload) { return payload.toUpperCase(); }
Defensive patterns

Strategy: validation

Validate before calling

// build-time check: ensure the @ExactlyOnce method returns a non-void type
if (void.class.equals(method.getReturnType()) || Void.class.equals(method.getReturnType())) {
    throw new IllegalStateException("@ExactlyOnce method " + method.getName() + " must return a value");
}

Prevention

When it happens

Trigger: Annotating a @Incoming/@Outgoing connector method with @ExactlyOnce whose return type is void, or Uni<Void>/CompletionStage<Void> (the reactive variant is checked in error 2172).

Common situations: Developers copy a standard @Incoming processor that only consumes (void return) and add @ExactlyOnce to get transactional semantics; or they refactor a method to @ExactlyOnce without realizing the outgoing channel requires a produced value.

Related errors


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