{"record":{"id":"e30ff86a37428509","repo":"quarkusio/quarkus","slug":"kotlin-suspend-functions-in-websockets-next-endp","errorCode":null,"errorMessage":"Kotlin `suspend` functions in WebSockets Next endpoints may not be annotated @Blocking, @NonBlocking or @RunOnVirtualThread: ","messagePattern":"Kotlin `suspend` functions in WebSockets Next endpoints may not be annotated @Blocking, @NonBlocking or @RunOnVirtualThread: ","errorType":"validation","errorClass":"WebSocketException","httpStatus":null,"severity":"error","filePath":"extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java","lineNumber":1945,"sourceCode":"                                callback.asString()));\n            }\n            if (validator != null) {\n                validator.accept(callback);\n            }\n            return callback;\n        }\n        throw new WebSocketException(\n                String.format(\"There can be only one callback annotated with %s declared on %s\", annotationName, beanClass));\n    }\n\n    private static ExecutionModel executionModel(MethodInfo method, TransformedAnnotationsBuildItem transformedAnnotations) {\n        if (KotlinUtils.isKotlinSuspendMethod(method)\n                && (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)\n                        || transformedAnnotations.hasAnnotation(method.declaringClass(),\n                                WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)\n                        || transformedAnnotations.hasAnnotation(method, WebSocketDotNames.BLOCKING)\n                        || transformedAnnotations.hasAnnotation(method, WebSocketDotNames.NON_BLOCKING))) {\n            throw new WebSocketException(\"Kotlin `suspend` functions in WebSockets Next endpoints may not be \"\n                    + \"annotated @Blocking, @NonBlocking or @RunOnVirtualThread: \" + method);\n        }\n        if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)\n                || transformedAnnotations.hasAnnotation(method.declaringClass(), WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)) {\n            return ExecutionModel.VIRTUAL_THREAD;\n        } else if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.BLOCKING)) {\n            return ExecutionModel.WORKER_THREAD;\n        } else if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.NON_BLOCKING)) {\n            return ExecutionModel.EVENT_LOOP;\n        } else if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.TRANSACTIONAL)\n                || transformedAnnotations.hasAnnotation(method.declaringClass(), WebSocketDotNames.TRANSACTIONAL)) {\n            // Method annotated with @Transactional or declared on a class annotated @Transactional is also treated as a blocking method\n            return ExecutionModel.WORKER_THREAD;\n        } else {\n            return hasBlockingSignature(method) ? ExecutionModel.WORKER_THREAD : ExecutionModel.EVENT_LOOP;\n        }\n    }\n","sourceCodeStart":1927,"sourceCodeEnd":1963,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java#L1927-L1963","documentation":"Kotlin suspend functions in WebSockets Next endpoints are dispatched on the Vert.x context, so they cannot also declare an execution model via @Blocking, @NonBlocking, or @RunOnVirtualThread. If a suspend function (or its class, for @RunOnVirtualThread) carries one of these annotations, the build fails with WebSocketException.","triggerScenarios":"Annotating a Kotlin `suspend` callback (e.g. @OnMessage suspend fun ...) with @Blocking, @NonBlocking, or @RunOnVirtualThread, or annotating the endpoint class with @RunOnVirtualThread while it has suspend callbacks.","commonSituations":"Converting Java handlers to Kotlin and keeping the @RunOnVirtualThread/@Blocking annotations; cargo-culting annotations from other Quarkus reactive endpoints.","solutions":["Remove @Blocking, @NonBlocking and @RunOnVirtualThread from the suspend function (and the class if it only has suspend callbacks)","Use Kotlin coroutines primitives (dispatchers inside the function) instead of Quarkus execution-model annotations","Convert to a non-suspend Java-style method with @RunOnVirtualThread/@Blocking if thread-based execution is truly required"],"exampleFix":"// before\n@OnTextMessage\n@RunOnVirtualThread\nsuspend fun onMessage(msg: String) {}\n// after\n@OnTextMessage\nsuspend fun onMessage(msg: String) = withContext(Dispatchers.Default) { /* ... */ }","handlingStrategy":"validation","validationCode":"void checkSuspendAnnotations(Method m) {\n    if (!m.isAnnotationPresent(OnTextMessage.class)) return;\n    for (Class<? extends Annotation> bad :\n            List.of(Blocking.class, NonBlocking.class, RunOnVirtualThread.class)) {\n        if (m.isAnnotationPresent(bad))\n            throw new IllegalStateException(\"Kotlin suspend callbacks must not use \" + bad.getSimpleName());\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never combine Kotlin suspend callbacks with @Blocking/@NonBlocking/@RunOnVirtualThread","Use withContext/dispatchers inside the suspend function for threading needs"],"tags":["kotlin","websockets","build-time-validation"],"backgroundTag":"conflicting-execution-model-annotations","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}