{"record":{"id":"116ef1be5e33cacb","repo":"quarkusio/quarkus","slug":"multiple-global-onerror-callbacks-may-not-accept","errorCode":null,"errorMessage":"Multiple global @OnError callbacks may not accept the same error parameter: %s\n\t- %s\n\t- %s","messagePattern":"Multiple global @OnError callbacks may not accept the same error parameter: (.+?)\n\t- (.+?)\n\t- (.+?)","errorType":"exception","errorClass":"WebSocketException","httpStatus":null,"severity":"error","filePath":"extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java","lineNumber":303,"sourceCode":"            CallbackArgumentsBuildItem callbackArguments,\n            TransformedAnnotationsBuildItem transformedAnnotations) {\n\n        IndexView index = beanArchiveIndex.getIndex();\n\n        // Collect global error handlers, i.e. handlers that are not declared on an endpoint\n        Map<DotName, GlobalErrorHandler> globalErrors = new HashMap<>();\n        Set<DotName> unremovableBeanClasses = new HashSet<>();\n        for (BeanInfo bean : beanDiscoveryFinished.beanStream().classBeans()) {\n            ClassInfo beanClass = bean.getTarget().get().asClass();\n            if (beanClass.declaredAnnotation(WebSocketDotNames.WEB_SOCKET) == null\n                    && beanClass.declaredAnnotation(WebSocketDotNames.WEB_SOCKET_CLIENT) == null) {\n                List<Callback> errorHandlers = findErrorHandlers(Target.UNDEFINED, index, bean, beanClass,\n                        callbackArguments, transformedAnnotations, null);\n                for (Callback callback : errorHandlers) {\n                    GlobalErrorHandler errorHandler = new GlobalErrorHandler(bean, callback);\n                    DotName errorTypeName = callback.argumentType(ErrorCallbackArgument::isError).name();\n                    if (globalErrors.containsKey(errorTypeName)) {\n                        throw new WebSocketException(String.format(\n                                \"Multiple global @OnError callbacks may not accept the same error parameter: %s\\n\\t- %s\\n\\t- %s\",\n                                errorTypeName,\n                                callback.asString(),\n                                globalErrors.get(errorTypeName).callback.asString()));\n                    }\n                    globalErrors.put(errorTypeName, errorHandler);\n                }\n                if (!errorHandlers.isEmpty()) {\n                    unremovableBeanClasses.add(beanClass.name());\n                }\n            }\n        }\n        globalErrorHandlers.produce(new GlobalErrorHandlersBuildItem(List.copyOf(globalErrors.values())));\n        unremovableBean.produce(UnremovableBeanBuildItem.beanTypes(unremovableBeanClasses));\n    }\n\n    @BuildStep\n    void collectEndpoints(BeanArchiveIndexBuildItem beanArchiveIndex,","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java#L285-L321","documentation":"Quarkus websockets-next allows global @OnError handlers, but at most one global handler may accept a given error type. During deployment, collectGlobalErrorHandlers indexes each global handler by its error parameter type; a duplicate type is ambiguous (which handler should run?) and fails the build.","triggerScenarios":"Declaring two global error handler beans (e.g. two @ApplicationScoped classes or two methods annotated with @OnError with GlobalError.Target.UNDEFINED semantics) whose @OnError methods both take, say, an UnhandledFailure parameter or both take Throwable.","commonSituations":"Adding a second global error handler in a shared library that already provides one; refactoring an endpoint @OnError into a global one while the old global handler remains; duplicate error types like Throwable and its implicit clash.","solutions":["Remove or merge one of the two conflicting global @OnError handlers","Change one handler's error parameter type so the types differ","Move one handler to a specific endpoint as a regular @OnError callback instead of a global one"],"exampleFix":"// before\n@OnError(unhandled = UnhandledFailure.ERROR) void a(Throwable t) {}\n@OnError(unhandled = UnhandledFailure.ERROR) void b(Throwable t) {} // duplicate\n// after\n@OnError(unhandled = UnhandledFailure.ERROR) void a(Throwable t) {}\n@OnError(unhandled = UnhandledFailure.ERROR) void b(WebSocketConnection c, Throwable t) {} // different param set / or remove","handlingStrategy":"validation","validationCode":"// Startup-time check in dev/test: ensure only one global handler per error type\nSet<Class<?>> seen = new HashSet<>();\nfor (Class<?> h : globalErrorHandlers) {\n  if (!seen.add(h)) throw new IllegalStateException(\"Duplicate global error handler: \" + h);\n}","typeGuard":null,"tryCatchPattern":"try {\n  app.start();\n} catch (WebSocketException e) {\n  if (e.getMessage().contains(\"Multiple global @OnError\")) {\n    log.error(\"Merge or remove duplicate global error handlers\");\n  }\n  throw e;\n}","preventionTips":["Centralize global error handling in a single class","Before adding a global @OnError, search the project and shared libraries for existing ones","Keep error parameter types distinct if multiple handlers are truly needed"],"tags":["websocket","build-time","error-handler","duplicate"],"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-12T22:17:10.623Z"}