quarkusio/quarkus · error · WebSocketException

Multiple global @OnError callbacks may not accept the same e

Error message

Multiple global @OnError callbacks may not accept the same error parameter: %s
	- %s
	- %s

What it means

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.

Source

Thrown at extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java:303

            CallbackArgumentsBuildItem callbackArguments,
            TransformedAnnotationsBuildItem transformedAnnotations) {

        IndexView index = beanArchiveIndex.getIndex();

        // Collect global error handlers, i.e. handlers that are not declared on an endpoint
        Map<DotName, GlobalErrorHandler> globalErrors = new HashMap<>();
        Set<DotName> unremovableBeanClasses = new HashSet<>();
        for (BeanInfo bean : beanDiscoveryFinished.beanStream().classBeans()) {
            ClassInfo beanClass = bean.getTarget().get().asClass();
            if (beanClass.declaredAnnotation(WebSocketDotNames.WEB_SOCKET) == null
                    && beanClass.declaredAnnotation(WebSocketDotNames.WEB_SOCKET_CLIENT) == null) {
                List<Callback> errorHandlers = findErrorHandlers(Target.UNDEFINED, index, bean, beanClass,
                        callbackArguments, transformedAnnotations, null);
                for (Callback callback : errorHandlers) {
                    GlobalErrorHandler errorHandler = new GlobalErrorHandler(bean, callback);
                    DotName errorTypeName = callback.argumentType(ErrorCallbackArgument::isError).name();
                    if (globalErrors.containsKey(errorTypeName)) {
                        throw new WebSocketException(String.format(
                                "Multiple global @OnError callbacks may not accept the same error parameter: %s\n\t- %s\n\t- %s",
                                errorTypeName,
                                callback.asString(),
                                globalErrors.get(errorTypeName).callback.asString()));
                    }
                    globalErrors.put(errorTypeName, errorHandler);
                }
                if (!errorHandlers.isEmpty()) {
                    unremovableBeanClasses.add(beanClass.name());
                }
            }
        }
        globalErrorHandlers.produce(new GlobalErrorHandlersBuildItem(List.copyOf(globalErrors.values())));
        unremovableBean.produce(UnremovableBeanBuildItem.beanTypes(unremovableBeanClasses));
    }

    @BuildStep
    void collectEndpoints(BeanArchiveIndexBuildItem beanArchiveIndex,

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove or merge one of the two conflicting global @OnError handlers
  2. Change one handler's error parameter type so the types differ
  3. Move one handler to a specific endpoint as a regular @OnError callback instead of a global one

Example fix

// before
@OnError(unhandled = UnhandledFailure.ERROR) void a(Throwable t) {}
@OnError(unhandled = UnhandledFailure.ERROR) void b(Throwable t) {} // duplicate
// after
@OnError(unhandled = UnhandledFailure.ERROR) void a(Throwable t) {}
@OnError(unhandled = UnhandledFailure.ERROR) void b(WebSocketConnection c, Throwable t) {} // different param set / or remove
Defensive patterns

Strategy: validation

Validate before calling

// Startup-time check in dev/test: ensure only one global handler per error type
Set<Class<?>> seen = new HashSet<>();
for (Class<?> h : globalErrorHandlers) {
  if (!seen.add(h)) throw new IllegalStateException("Duplicate global error handler: " + h);
}

Try / catch

try {
  app.start();
} catch (WebSocketException e) {
  if (e.getMessage().contains("Multiple global @OnError")) {
    log.error("Merge or remove duplicate global error handlers");
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/116ef1be5e33cacb. Report an issue: GitHub.