{"record":{"id":"6ee1688ea9abbf37","repo":"quarkusio/quarkus","slug":"currently-suspend-methods-for-reactive-messaging-a","errorCode":null,"errorMessage":"Currently suspend methods for Reactive Messaging are not supported on methods that are only annotated with @Outgoing","messagePattern":"Currently suspend methods for Reactive Messaging are not supported on methods that are only annotated with @Outgoing","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java","lineNumber":443,"sourceCode":"            baseName = DotNames.simpleName(bean.getImplClazz().enclosingClass()) + \"_\"\n                    + DotNames.simpleName(bean.getImplClazz().name());\n        } else {\n            baseName = DotNames.simpleName(bean.getImplClazz().name());\n        }\n        StringBuilder sigBuilder = new StringBuilder();\n        sigBuilder.append(method.name()).append(\"_\").append(method.returnType().name().toString());\n        for (Type i : method.parameterTypes()) {\n            sigBuilder.append(i.name().toString());\n        }\n        String targetPackage = DotNames.internalPackageNameWithTrailingSlash(bean.getImplClazz().name());\n        String generatedName = targetPackage + baseName\n                + INVOKER_SUFFIX + \"_\" + method.name() + \"_\"\n                + HashUtil.sha1(sigBuilder.toString());\n\n        if (isSuspendMethod\n                && ((mediatorConfiguration.getIncoming().isEmpty()) && (mediatorConfiguration.getOutgoing() != null))) {\n            // TODO: this restriction needs to be lifted\n            throw new IllegalStateException(\n                    \"Currently suspend methods for Reactive Messaging are not supported on methods that are only annotated with @Outgoing\");\n        }\n\n        if (!isSuspendMethod) {\n            generateStandardInvoker(method, gizmo, generatedName);\n        } else if (!mediatorConfiguration.getIncoming().isEmpty()) {\n            generateSubscribingCoroutineInvoker(method, gizmo, generatedName);\n        }\n\n        return generatedName.replace('/', '.');\n    }\n\n    private void generateStandardInvoker(MethodInfo method, Gizmo gizmo, String generatedName) {\n        String dotName = generatedName.replace('/', '.');\n        gizmo.class_(dotName, cc -> {\n            cc.implements_(Invoker.class);\n\n            String beanInstanceType = method.declaringClass().name().toString();","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java#L425-L461","documentation":"Kotlin suspend functions are supported as Reactive Messaging mediators, but not when the method is only @Outgoing (a publisher). The invoker generation step throws IllegalStateException because the TODO'd publisher-side coroutine support is unimplemented. @Incoming (or incoming+outgoing) suspend methods are fine.","triggerScenarios":"Declaring `suspend fun` combined with only @Outgoing; the deployment then generates the invoker and hits this check in generatedInvokerName/generateInvoker.","commonSituations":"Kotlin users writing periodic producers with @Outgoing as suspend functions after seeing suspend+@Incoming examples work.","solutions":["Remove `suspend` from the @Outgoing-only method and return a reactive type (Multi/Uni/Publisher) instead.","Do the async work inside a coroutineScope launched from a non-suspend method, returning the resulting Multi/Uni.","Convert the method to include an @Incoming stream so suspend is supported."],"exampleFix":"// before\n@Outgoing(\"out\")\nsuspend fun produce(): String = compute()\n// after\n@Outgoing(\"out\")\nfun produce(): Uni<String> = Uni.createFrom().item { runBlocking { compute() } }","handlingStrategy":"validation","validationCode":"// Ensure suspend methods always have an @Incoming:\nfun isValidSuspendMediator(hasIncoming: Boolean, hasOutgoing: Boolean, isSuspend: Boolean): Boolean =\n    !isSuspend || hasIncoming","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only mark mediators with an @Incoming stream as suspend.","For producers, use Multi/Uni return types instead of suspend.","Track SmallRye Reactive Messaging releases — this TODO restriction may be lifted."],"tags":["kotlin","coroutines","reactive-messaging","quarkus","unsupported"],"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"}