quarkusio/quarkus · error · WebSocketException

There can be only one callback annotated with %s declared on

Error message

There can be only one callback annotated with %s declared on %s

What it means

WebSockets Next endpoints allow at most one callback method per lifecycle/message annotation (e.g. only one @OnTextMessage per message type, one @OnOpen, one @OnClose). If more than one callback annotated with the same annotation is declared on the same endpoint bean class, the build fails with this WebSocketException.

Source

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

                    throw new WebSocketException(
                            String.format("@%s callback must not accept a message parameter; found %s: %s",
                                    DotNames.simpleName(callback.annotation.name()),
                                    messageArguments,
                                    callback.asString()));
                }
            }
            if (target == Target.CLIENT && callback.broadcast()) {
                throw new WebSocketClientException(
                        String.format("@%s callback declared on a client endpoint must not broadcast messages: %s",
                                DotNames.simpleName(callback.annotation.name()),
                                callback.asString()));
            }
            if (validator != null) {
                validator.accept(callback);
            }
            return callback;
        }
        throw new WebSocketException(
                String.format("There can be only one callback annotated with %s declared on %s", annotationName, beanClass));
    }

    private static ExecutionModel executionModel(MethodInfo method, TransformedAnnotationsBuildItem transformedAnnotations) {
        if (KotlinUtils.isKotlinSuspendMethod(method)
                && (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)
                        || transformedAnnotations.hasAnnotation(method.declaringClass(),
                                WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)
                        || transformedAnnotations.hasAnnotation(method, WebSocketDotNames.BLOCKING)
                        || transformedAnnotations.hasAnnotation(method, WebSocketDotNames.NON_BLOCKING))) {
            throw new WebSocketException("Kotlin `suspend` functions in WebSockets Next endpoints may not be "
                    + "annotated @Blocking, @NonBlocking or @RunOnVirtualThread: " + method);
        }
        if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)
                || transformedAnnotations.hasAnnotation(method.declaringClass(), WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)) {
            return ExecutionModel.VIRTUAL_THREAD;
        } else if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.BLOCKING)) {
            return ExecutionModel.WORKER_THREAD;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Keep only one method per callback annotation (per message type) and merge logic into it
  2. Move the second handler into a helper method called from the single callback
  3. Use distinct message parameter types if you truly need separate @OnTextMessage handlers

Example fix

// before
class Sock {
    @OnOpen void open1() {}
    @OnOpen void open2() {}
}
// after
class Sock {
    @OnOpen void open() { open1(); open2(); }
    void open1() {}
    void open2() {}
}
Defensive patterns

Strategy: validation

Validate before calling

void checkSingleCallback(Class<?> endpoint, Class<? extends Annotation> ann) {
    long count = Arrays.stream(endpoint.getDeclaredMethods())
        .filter(m -> m.isAnnotationPresent(ann)).count();
    if (count > 1) throw new IllegalStateException("Only one @" + ann.getSimpleName() + " allowed on " + endpoint);
}

Prevention

When it happens

Trigger: Declaring two methods with the same callback annotation (e.g. two @OnOpen methods, or two @OnTextMessage methods accepting the same type) on the same endpoint class; detected during build-time processing when iterating callbacks.

Common situations: Merging code from two endpoint classes; adding an overloaded handler thinking signatures differentiate them; copy-paste duplication.

Related errors


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