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
- Keep only one method per callback annotation (per message type) and merge logic into it
- Move the second handler into a helper method called from the single callback
- 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
- Keep exactly one method per callback annotation per endpoint class
- Merge handler logic into helper methods invoked by the single callback
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
- @%s callback declared on a client endpoint must not broadcas
- Kotlin `suspend` functions in WebSockets Next endpoints may
- Unsupported return type:
- Type ${className} must be annotated with @Embeddable, becaus
- Fragment [<fragmentId>] not defined in template <templateId>
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/cb1c8b237c981286.
Report an issue: GitHub.