{"record":{"id":"b330f7e83c196320","repo":"quarkusio/quarkus","slug":"exactlyonce-on-method-methodname-must-return-a","errorCode":null,"errorMessage":"@ExactlyOnce on method ${methodName} must return a value to produce to the outgoing channel","messagePattern":"@ExactlyOnce on method (.+?) must return a value to produce to the outgoing channel","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java","lineNumber":169,"sourceCode":"                        \"@ExactlyOnce on method \" + methodName + \" requires both @Incoming and @Outgoing annotations\");\n            }\n\n            if (method.parametersCount() == 0) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName + \" requires at least one parameter\");\n            }\n\n            for (Type paramType : method.parameterTypes()) {\n                if (paramType.name().equals(DotNames.MESSAGE)) {\n                    throw new IllegalArgumentException(\n                            \"@ExactlyOnce on method \" + methodName\n                                    + \" does not support Message parameters, use payload types instead\");\n                }\n            }\n\n            if (method.returnType().name().equals(DotNames.VOID)\n                    || method.returnType().name().equals(VOID_BOXED)) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName\n                                + \" must return a value to produce to the outgoing channel\");\n            }\n\n            if (method.hasAnnotation(DotNames.BLOCKING) || method.hasAnnotation(DotNames.SMALLRYE_BLOCKING)) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName\n                                + \" cannot be combined with @Blocking\");\n            }\n\n            DotName returnTypeName = method.returnType().name();\n            boolean reactive = DotNames.UNI.equals(returnTypeName) || DotNames.MULTI.equals(returnTypeName)\n                    || DotNames.COMPLETION_STAGE.equals(returnTypeName);\n\n            if (reactive && method.returnType().kind() == Type.Kind.PARAMETERIZED_TYPE) {\n                DotName typeArg = method.returnType().asParameterizedType().arguments().get(0).name();\n                if (typeArg.equals(VOID_BOXED)) {\n                    throw new IllegalArgumentException(","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java#L151-L187","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Change the method to return the payload (or Message) that should be sent to the outgoing channel.","If the method does not need to produce anything, remove @ExactlyOnce and use plain @Transactional / manual acknowledgement instead.","Switch the outgoing channel semantics: if you only need at-least-once, drop @ExactlyOnce."],"exampleFix":"// before\n@Incoming(\"in\")\n@Outgoing(\"out\")\n@ExactlyOnce\npublic void process(String payload) { ... }\n\n// after\n@Incoming(\"in\")\n@Outgoing(\"out\")\n@ExactlyOnce\npublic String process(String payload) { return payload.toUpperCase(); }","handlingStrategy":"validation","validationCode":"// build-time check: ensure the @ExactlyOnce method returns a non-void type\nif (void.class.equals(method.getReturnType()) || Void.class.equals(method.getReturnType())) {\n    throw new IllegalStateException(\"@ExactlyOnce method \" + method.getName() + \" must return a value\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always return the produced payload or Message from @ExactlyOnce methods","Never annotate consumer-only (void) methods with @ExactlyOnce","Run the Quarkus build early; this is caught at deployment time"],"tags":["kafka","reactive-messaging","exactly-once","build-time-validation"],"backgroundTag":"exactly-once-void-return","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}