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
- 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
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
- 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
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
- Multiple endpoints [%s, %s] define the same endpoint id: %s
- Multiple client endpoints [%s, %s] define the same endpoint
- Multiple checked templates exist for the template path %s:
- Only a single instance of '${annotation}' is allowed per RES
- Detected two @PermissionChecker annotations with same value
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/116ef1be5e33cacb.
Report an issue: GitHub.