{"record":{"id":"cb1c8b237c981286","repo":"quarkusio/quarkus","slug":"there-can-be-only-one-callback-annotated-with-s-d","errorCode":null,"errorMessage":"There can be only one callback annotated with %s declared on %s","messagePattern":"There can be only one callback annotated with (.+?) declared on (.+?)","errorType":"validation","errorClass":"WebSocketException","httpStatus":null,"severity":"error","filePath":"extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java","lineNumber":1934,"sourceCode":"                    throw new WebSocketException(\n                            String.format(\"@%s callback must not accept a message parameter; found %s: %s\",\n                                    DotNames.simpleName(callback.annotation.name()),\n                                    messageArguments,\n                                    callback.asString()));\n                }\n            }\n            if (target == Target.CLIENT && callback.broadcast()) {\n                throw new WebSocketClientException(\n                        String.format(\"@%s callback declared on a client endpoint must not broadcast messages: %s\",\n                                DotNames.simpleName(callback.annotation.name()),\n                                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;","sourceCodeStart":1916,"sourceCodeEnd":1952,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java#L1916-L1952","documentation":"WebSockets Next endpoints allow at most one callback method per lifecycle/message annotation (e.g. only one @OnTextMessage per message type, one @OnOpen, one @OnClose). If more than one callback annotated with the same annotation is declared on the same endpoint bean class, the build fails with this WebSocketException.","triggerScenarios":"Declaring two methods with the same callback annotation (e.g. two @OnOpen methods, or two @OnTextMessage methods accepting the same type) on the same endpoint class; detected during build-time processing when iterating callbacks.","commonSituations":"Merging code from two endpoint classes; adding an overloaded handler thinking signatures differentiate them; copy-paste duplication.","solutions":["Keep only one method per callback annotation (per message type) and merge logic into it","Move the second handler into a helper method called from the single callback","Use distinct message parameter types if you truly need separate @OnTextMessage handlers"],"exampleFix":"// before\nclass Sock {\n    @OnOpen void open1() {}\n    @OnOpen void open2() {}\n}\n// after\nclass Sock {\n    @OnOpen void open() { open1(); open2(); }\n    void open1() {}\n    void open2() {}\n}","handlingStrategy":"validation","validationCode":"void checkSingleCallback(Class<?> endpoint, Class<? extends Annotation> ann) {\n    long count = Arrays.stream(endpoint.getDeclaredMethods())\n        .filter(m -> m.isAnnotationPresent(ann)).count();\n    if (count > 1) throw new IllegalStateException(\"Only one @\" + ann.getSimpleName() + \" allowed on \" + endpoint);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep exactly one method per callback annotation per endpoint class","Merge handler logic into helper methods invoked by the single callback"],"tags":["websockets","build-time-validation","duplicate-callback"],"backgroundTag":"duplicate-callback-method","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"}