quarkusio/quarkus · error · WebSocketServerException

Multiple client endpoints

Error message

Multiple client endpoints [%s, %s] define the same endpoint id: %s

What it means

Client endpoints also require unique ids (from @WebSocketClient(clientId=...) or the class name by default). collectEndpoints detects a second client endpoint reusing an existing id and throws WebSocketServerException because client lookups by id would be ambiguous.

Solutions

  1. Change the clientId on one of the client endpoints
  2. Remove the explicit clientId to fall back to the unique class-name id
  3. Grep for the duplicated clientId to locate all usages, including in connection-open code

Example fix

// before
@WebSocketClient(path="/a", clientId="client") class C1 {}
@WebSocketClient(path="/b", clientId="client") class C2 {}
// after
@WebSocketClient(path="/b", clientId="client2") class C2 {}
Defensive patterns

Strategy: validation

Validate before calling

@Test
void clientIdsUnique() {
  Set<String> ids = new HashSet<>();
  for (Class<?> c : allClients) {
    WebSocketClient cl = c.getAnnotation(WebSocketClient.class);
    String id = cl.clientId().isEmpty() ? c.getName() : cl.clientId();
    if (!ids.add(id)) throw new AssertionError("Duplicate clientId: " + id);
  }
}

Prevention

When it happens

Trigger: Two @WebSocketClient classes declaring the same clientId value, or an explicit clientId matching another client endpoint class's implicit id.

Common situations: Copy-paste of client classes retaining clientId; renaming classes so defaults collide after explicit ids were normalized; generated clients sharing a template id.

Related errors


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

Appendix: source

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

                inboundProcessingMode = webSocketAnnotation.value("inboundProcessingMode");
            } else {
                target = Target.CLIENT;
                path = getPath(webSocketClientAnnotation.value("path").asString());
                DotName prevPath = clientPathToEndpoint.put(path, beanClass.name());
                if (prevPath != null) {
                    throw new WebSocketServerException(
                            String.format("Multiple client endpoints [%s, %s] define the same path: %s", prevPath, beanClass,
                                    path));
                }
                AnnotationValue clientIdValue = webSocketClientAnnotation.value("clientId");
                if (clientIdValue == null) {
                    id = beanClass.name().toString();
                } else {
                    id = clientIdValue.asString();
                }
                DotName prevId = clientIdToEndpoint.put(id, beanClass.name());
                if (prevId != null) {
                    throw new WebSocketServerException(
                            String.format("Multiple client endpoints [%s, %s] define the same endpoint id: %s", prevId,
                                    beanClass,
                                    id));
                }
                inboundProcessingMode = webSocketClientAnnotation.value("inboundProcessingMode");
            }

            Callback onOpen = findCallback(target, beanArchiveIndex.getIndex(), bean, beanClass,
                    WebSocketDotNames.ON_OPEN, callbackArguments, transformedAnnotations, path);
            Callback onTextMessage = findCallback(target, beanArchiveIndex.getIndex(), bean, beanClass,
                    WebSocketDotNames.ON_TEXT_MESSAGE, callbackArguments, transformedAnnotations, path);
            Callback onBinaryMessage = findCallback(target, beanArchiveIndex.getIndex(), bean, beanClass,
                    WebSocketDotNames.ON_BINARY_MESSAGE, callbackArguments, transformedAnnotations, path);
            Callback onPingMessage = findCallback(target, beanArchiveIndex.getIndex(), bean, beanClass,
                    WebSocketDotNames.ON_PING_MESSAGE, callbackArguments, transformedAnnotations, path,
                    this::validateOnPingMessage);
            Callback onPongMessage = findCallback(target, beanArchiveIndex.getIndex(), bean, beanClass,
                    WebSocketDotNames.ON_PONG_MESSAGE, callbackArguments, transformedAnnotations, path,

View on GitHub (pinned to e1c734241f)