{"record":{"id":"667812a37de616bc","repo":"quarkusio/quarkus","slug":"exactlyonce-on-method-methodname-does-not-supp-667812","errorCode":null,"errorMessage":"@ExactlyOnce on method ${methodName} does not support Kotlin suspend functions","messagePattern":"@ExactlyOnce on method (.+?) does not support Kotlin suspend functions","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":209,"sourceCode":"            }\n\n            if (method.hasAnnotation(DotNames.WITH_TRANSACTION) && !reactive) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName\n                                + \" cannot combine @WithTransaction with a synchronous return type\"\n                                + \", use @Transactional instead\");\n            }\n\n            if (method.hasAnnotation(DotNames.TRANSACTIONAL) && method.hasAnnotation(DotNames.WITH_TRANSACTION)) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName\n                                + \" cannot combine @Transactional with @WithTransaction\");\n            }\n\n            boolean isSuspend = method.parameterTypes().stream()\n                    .anyMatch(t -> t.name().equals(DotNames.CONTINUATION));\n            if (isSuspend) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName\n                                + \" does not support Kotlin suspend functions\");\n            }\n\n            String incomingChannel = incoming.value().asString();\n            String outgoingChannel = outgoing.value().asString();\n\n            if (!discoveryState.isKafkaConnector(channelsManagedByConnectors, true, incomingChannel)) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName\n                                + \": incoming channel '\" + incomingChannel + \"' is not managed by the Kafka connector\");\n            }\n            if (!discoveryState.isKafkaConnector(channelsManagedByConnectors, false, outgoingChannel)) {\n                throw new IllegalArgumentException(\n                        \"@ExactlyOnce on method \" + methodName\n                                + \": outgoing channel '\" + outgoingChannel + \"' is not managed by the Kafka connector\");\n            }\n","sourceCodeStart":191,"sourceCodeEnd":227,"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#L191-L227","documentation":"Kotlin suspend functions compile to methods taking a Continuation parameter and return an opaque Object/Coroutine return type; the exactly-once invoker and its transactional wrapper are not implemented against Kotlin coroutines, so the extension rejects suspend functions at build time.","triggerScenarios":"An @ExactlyOnce method written in Kotlin is declared as `suspend fun`, detected via a Continuation parameter in the method's parameter types.","commonSituations":"Kotlin developers writing suspend processors as they would for plain reactive messaging, then adding @ExactlyOnce for transactional semantics.","solutions":["Remove the `suspend` modifier and return Uni/Multi (or a plain type) instead; use kotlinx-coroutines-reactor/mutiny bridges (e.g. uni.await) inside the non-suspend method.","Handle coroutine work inside the method body rather than making the signature suspend.","Drop @ExactlyOnce if suspend semantics are essential."],"exampleFix":"// before\n@ExactlyOnce\n@Incoming(\"in\") @Outgoing(\"out\")\nsuspend fun process(p: String): String { ... }\n\n// after\n@ExactlyOnce\n@Incoming(\"in\") @Outgoing(\"out\")\nfun process(p: String): Uni<String> = uni { ... } // bridge coroutines internally","handlingStrategy":"validation","validationCode":"// Kotlin: do not declare suspend on @ExactlyOnce functions\n// (compile-time check in JVM terms: Continuation must not appear in parameter types)\nrequire(!method.parameters.any { it.type == Continuation::class }) { \"suspend not supported with @ExactlyOnce\" }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid `suspend` on @ExactlyOnce functions; bridge coroutines inside a normal fun returning Uni","Keep Kafka processors in Kotlin non-suspend when using exactly-once"],"tags":["kotlin","kafka","reactive-messaging","exactly-once"],"backgroundTag":"suspend-function-unsupported","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"}