{"record":{"id":"fadeaae32327bf2f","repo":"quarkusio/quarkus","slug":"multiple-onerror-callbacks-may-not-accept-the-sam","errorCode":null,"errorMessage":"Multiple @OnError callbacks may not accept the same error parameter: %s\n\t- %s\n\t- %s","messagePattern":"Multiple @OnError callbacks may not accept the same error parameter: (.+?)\n\t- (.+?)\n\t- (.+?)","errorType":"validation","errorClass":"WebSocketException","httpStatus":null,"severity":"error","filePath":"extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java","lineNumber":1321,"sourceCode":"                    InvokerInfo invoker = invokerFactory.createInvoker(callback.bean, callback.method)\n                            .withInvocationWrapper(CoroutineInvoker.class, \"inNewCoroutine\")\n                            .build();\n                    bc.yield(bc.new_(ConstructorDesc.of(invoker.getClassDesc())));\n                });\n            });\n        }\n        return null;\n    }\n\n    private static List<ErrorHandler> generateErrorHandlers(io.quarkus.gizmo2.creator.ClassCreator cc,\n            WebSocketEndpointBuildItem endpoint, GlobalErrorHandlersBuildItem globalErrorHandlers, IndexView index,\n            InvokerFactoryBuildItem invokerFactory) {\n        List<ErrorHandler> result = new ArrayList<>();\n        Map<DotName, Callback> found = new HashMap<>();\n        for (Callback callback : endpoint.onErrors) {\n            DotName errorTypeName = callback.argumentType(ErrorCallbackArgument::isError).name();\n            if (found.containsKey(errorTypeName)) {\n                throw new WebSocketException(String.format(\n                        \"Multiple @OnError callbacks may not accept the same error parameter: %s\\n\\t- %s\\n\\t- %s\",\n                        errorTypeName, callback.asString(), found.get(errorTypeName).asString()));\n            }\n            found.put(errorTypeName, callback);\n            FieldDesc invoker = generateInvokerFieldIfNeeded(cc, callback,\n                    \"Error_\" + errorTypeName.withoutPackagePrefix(), invokerFactory);\n            result.add(new ErrorHandler(endpoint.bean, callback, invoker, throwableHierarchy(errorTypeName, index)));\n        }\n        List<GlobalErrorHandler> handlers = endpoint.isClient\n                ? globalErrorHandlers.forClient()\n                : globalErrorHandlers.forServer();\n        for (GlobalErrorHandler handler : handlers) {\n            Callback callback = handler.callback;\n            DotName errorTypeName = callback.argumentType(ErrorCallbackArgument::isError).name();\n            // Endpoint callbacks take precedence over global handlers\n            if (!found.containsKey(errorTypeName)) {\n                FieldDesc invoker = generateInvokerFieldIfNeeded(cc, callback,\n                        \"Error_\" + errorTypeName.withoutPackagePrefix(), invokerFactory);","sourceCodeStart":1303,"sourceCodeEnd":1339,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java#L1303-L1339","documentation":"An endpoint may register multiple @OnError callbacks, but each may only handle a distinct error type. When two @OnError methods accept the same error parameter type, the build fails with a WebSocketException listing the endpoint class and both conflicting callbacks.","triggerScenarios":"Two methods in one @WebSocket endpoint both annotated @OnError with the same error parameter type (e.g. both taking Exception or both taking a custom MyException).","commonSituations":"Copy-pasting an error handler and forgetting to remove the old one; two handlers added by different team members; merging branches that each added an @OnError for the same exception type.","solutions":["Remove the duplicate @OnError method, keeping one handler per error type","If handlers differ in behavior, narrow one to a more specific exception subclass","Consolidate logic into a single @OnError method"],"exampleFix":"// before\nclass MyWs {\n  @OnError void onErr(Exception e) {}\n  @OnError void onErr2(Exception e) {}\n}\n\n// after\nclass MyWs {\n  @OnError void onErr(Exception e) { /* single handler */ }\n}","handlingStrategy":"validation","validationCode":"var seen = new HashSet<Class<?>>();\nfor (Method m : MyWebSocket.class.getDeclaredMethods()) {\n    if (m.isAnnotationPresent(OnError.class)) {\n        var errTypes = Arrays.stream(m.getParameterTypes())\n            .filter(Throwable.class::isAssignableFrom).toList();\n        for (var t : errTypes) {\n            if (!seen.add(t)) throw new IllegalStateException(\"Duplicate @OnError for \" + t);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["One @OnError per exception type per endpoint","Search the endpoint class for existing @OnError before adding a new one","Use exception subclasses to differentiate handlers"],"tags":["quarkus","websockets-next","build-time-validation","duplicate-handler"],"backgroundTag":"duplicate-error-handler","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"}