quarkusio/quarkus · error · IllegalArgumentException

@ExactlyOnce on method ${methodName} does not support Messag

Error message

@ExactlyOnce on method ${methodName} does not support Message parameters, use payload types instead

What it means

With @ExactlyOnce, parameters must be plain payload types, not io.smallrye.reactive.messaging.Message<T>, because the transactional producer path re-wraps payloads itself and cannot round-trip framework Message metadata through the transaction. A Message-typed parameter fails the deployment.

Source

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

            MethodInfo method = annotation.target().asMethod();
            String methodName = method.declaringClass().name() + "#" + method.name();

            AnnotationInstance incoming = method.annotation(DotNames.INCOMING);
            AnnotationInstance outgoing = method.annotation(DotNames.OUTGOING);

            if (incoming == null || outgoing == null) {
                throw new IllegalArgumentException(
                        "@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");
            }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Change the parameter (and return) type from Message<T> to the raw payload T.
  2. If metadata is needed, access it via the incoming metadata on the payload type or restructure to use connector-provided metadata on the payload.
  3. Drop @ExactlyOnce if Message-level ack/nack control is required.

Example fix

// before
@Incoming("in") @Outgoing("out") @ExactlyOnce
Message<String> process(Message<String> in) { ... }
// after
@Incoming("in") @Outgoing("out") @ExactlyOnce
String process(String payload) { return transform(payload); }
Defensive patterns

Strategy: validation

Validate before calling

fun validateExactlyOnceParams(paramTypeNames: List<String>): Boolean =
    paramTypeNames.none { it.startsWith("io.smallrye.reactive.messaging.Message") }

Prevention

When it happens

Trigger: Declaring an @ExactlyOnce processing method like `Message<String> process(Message<String> in)`.

Common situations: Reusing an existing at-least-once processor that used Message for metadata/ack control, then adding @ExactlyOnce.

Related errors


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