quarkusio/quarkus · error · WebSocketException

Multiple @OnError callbacks may not accept the same error pa

Error message

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

What it means

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.

Source

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

                    InvokerInfo invoker = invokerFactory.createInvoker(callback.bean, callback.method)
                            .withInvocationWrapper(CoroutineInvoker.class, "inNewCoroutine")
                            .build();
                    bc.yield(bc.new_(ConstructorDesc.of(invoker.getClassDesc())));
                });
            });
        }
        return null;
    }

    private static List<ErrorHandler> generateErrorHandlers(io.quarkus.gizmo2.creator.ClassCreator cc,
            WebSocketEndpointBuildItem endpoint, GlobalErrorHandlersBuildItem globalErrorHandlers, IndexView index,
            InvokerFactoryBuildItem invokerFactory) {
        List<ErrorHandler> result = new ArrayList<>();
        Map<DotName, Callback> found = new HashMap<>();
        for (Callback callback : endpoint.onErrors) {
            DotName errorTypeName = callback.argumentType(ErrorCallbackArgument::isError).name();
            if (found.containsKey(errorTypeName)) {
                throw new WebSocketException(String.format(
                        "Multiple @OnError callbacks may not accept the same error parameter: %s\n\t- %s\n\t- %s",
                        errorTypeName, callback.asString(), found.get(errorTypeName).asString()));
            }
            found.put(errorTypeName, callback);
            FieldDesc invoker = generateInvokerFieldIfNeeded(cc, callback,
                    "Error_" + errorTypeName.withoutPackagePrefix(), invokerFactory);
            result.add(new ErrorHandler(endpoint.bean, callback, invoker, throwableHierarchy(errorTypeName, index)));
        }
        List<GlobalErrorHandler> handlers = endpoint.isClient
                ? globalErrorHandlers.forClient()
                : globalErrorHandlers.forServer();
        for (GlobalErrorHandler handler : handlers) {
            Callback callback = handler.callback;
            DotName errorTypeName = callback.argumentType(ErrorCallbackArgument::isError).name();
            // Endpoint callbacks take precedence over global handlers
            if (!found.containsKey(errorTypeName)) {
                FieldDesc invoker = generateInvokerFieldIfNeeded(cc, callback,
                        "Error_" + errorTypeName.withoutPackagePrefix(), invokerFactory);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove the duplicate @OnError method, keeping one handler per error type
  2. If handlers differ in behavior, narrow one to a more specific exception subclass
  3. Consolidate logic into a single @OnError method

Example fix

// before
class MyWs {
  @OnError void onErr(Exception e) {}
  @OnError void onErr2(Exception e) {}
}

// after
class MyWs {
  @OnError void onErr(Exception e) { /* single handler */ }
}
Defensive patterns

Strategy: validation

Validate before calling

var seen = new HashSet<Class<?>>();
for (Method m : MyWebSocket.class.getDeclaredMethods()) {
    if (m.isAnnotationPresent(OnError.class)) {
        var errTypes = Arrays.stream(m.getParameterTypes())
            .filter(Throwable.class::isAssignableFrom).toList();
        for (var t : errTypes) {
            if (!seen.add(t)) throw new IllegalStateException("Duplicate @OnError for " + t);
        }
    }
}

Prevention

When it happens

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

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

Related errors


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