{"record":{"id":"891b5ed69c655770","repo":"quarkusio/quarkus","slug":"exactlyonce-on-method-methodname-requires-both","errorCode":null,"errorMessage":"@ExactlyOnce on method ${methodName} requires both @Incoming and @Outgoing annotations","messagePattern":"@ExactlyOnce on method (.+?) requires both @Incoming and @Outgoing annotations","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":150,"sourceCode":"            CombinedIndexBuildItem combinedIndex,\n            List<ConnectorManagedChannelBuildItem> channelsManagedByConnectors,\n            BuildProducer<RunTimeConfigurationDefaultBuildItem> defaultConfigProducer,\n            BuildProducer<InjectedEmitterBuildItem> emitters) {\n\n        DefaultSerdeDiscoveryState discoveryState = new DefaultSerdeDiscoveryState(combinedIndex.getIndex());\n\n        for (AnnotationInstance annotation : combinedIndex.getIndex().getAnnotations(DotNames.EXACTLY_ONCE)) {\n            if (annotation.target().kind() != AnnotationTarget.Kind.METHOD) {\n                continue;\n            }\n            MethodInfo method = annotation.target().asMethod();\n            String methodName = method.declaringClass().name() + \"#\" + method.name();\n\n            AnnotationInstance incoming = method.annotation(DotNames.INCOMING);\n            AnnotationInstance outgoing = method.annotation(DotNames.OUTGOING);\n\n            if (incoming == null || outgoing == null) {\n                throw new IllegalArgumentException(\n                        \"@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)) {","sourceCodeStart":132,"sourceCodeEnd":168,"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#L132-L168","documentation":"The Kafka connector's @ExactlyOnce processing mode requires a method annotated @ExactlyOnce to be a full processing step: it must have both @Incoming and @Outgoing so the framework can coordinate commit of incoming offsets with publishing outgoing records. A method missing either annotation fails deployment with IllegalArgumentException.","triggerScenarios":"Annotating a method with @ExactlyOnce but only @Incoming, only @Outgoing, or neither.","commonSituations":"Copy-pasting @ExactlyOnce onto a consumer-only method; misunderstanding that exactly-once applies to the processing chain, not a plain consumer.","solutions":["Add both @Incoming and @Outgoing to the @ExactlyOnce method.","If only consuming, remove @ExactlyOnce and configure the connector's processing.garbage-collection / commit strategy instead.","Use normal at-least-once semantics with `quarkus.messaging.kafka... processing.failsafe` if a pure consumer is intended."],"exampleFix":"// before\n@Incoming(\"in\")\n@ExactlyOnce\nvoid consume(String m) { ... }\n// after\n@Incoming(\"in\")\n@Outgoing(\"out\")\n@ExactlyOnce\nString process(String m) { return transform(m); }","handlingStrategy":"validation","validationCode":"fun validateExactlyOnce(methodAnnotations: Set<String>): Boolean =\n    \"ExactlyOnce\" !in methodAnnotations ||\n        (\"Incoming\" in methodAnnotations && \"Outgoing\" in methodAnnotations)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember @ExactlyOnce applies to processing (in+out) methods only.","Review @ExactlyOnce usage in code review to catch misplaced annotations.","Read the Kafka connector docs' exactly-once section before adopting it."],"tags":["kafka","exactly-once","reactive-messaging","quarkus","deployment"],"backgroundTag":"exactly-once-annotation-misuse","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}