perwendel/spark · error · IllegalArgumentException
WebSocket handler must implement 'WebSocketListener' or be…
Error message
WebSocket handler must implement 'WebSocketListener' or be annotated as '@WebSocket'
What it means
Spark's Jetty WebSocket integration requires every registered WebSocket handler class to either implement Jetty's WebSocketListener interface or be annotated with Spark's @WebSocket annotation. validateHandlerClass performs this check before wrapping the class in a WebSocketHandlerWrapper. If neither condition holds, the class cannot be attached to a Jetty WebSocket servlet, so Spark fails fast with IllegalArgumentException.
Solutions
- Make the handler class implement org.eclipse.jetty.websocket.api.WebSocketListener (onWebSocketConnect/Close/Error/Message methods).
- Alternatively annotate the handler class with spark's @WebSocket annotation (with an onWebSocket... annotated method).
- Verify the correct annotation is imported (spark.websocket.WebSocket, not a Jetty or other library's WebSocket annotation).
Example fix
// before
class ChatHandler { /* no interface, no annotation */ }
webSocket("/chat", ChatHandler.class); // throws
// after
import spark.websocket.WebSocket;
import org.eclipse.jetty.websocket.api.*;
@WebSocket
class ChatHandler implements WebSocketListener {
public void onWebSocketConnect(Session s) { }
public void onWebSocketClose(int c, String m) { }
public void onWebSocketError(Throwable t) { }
public void onWebSocketText(String m) { }
public void onWebSocketBinary(byte[] p, int o, int l) { }
} Defensive patterns
Strategy: validation
Validate before calling
boolean valid = WebSocketListener.class.isAssignableFrom(HandlerClass.class)
|| HandlerClass.class.isAnnotationPresent(spark.websocket.WebSocket.class);
if (!valid) throw new IllegalArgumentException(HandlerClass + " is not a valid WebSocket handler"); Prevention
- Always implement WebSocketListener or annotate handlers with spark's @WebSocket.
- Add a unit test that reflectively validates every handler passed to webSocket(...).
- Double-check annotation imports when an IDE offers multiple @WebSocket types.
When it happens
Trigger: Calling WebSocketHandlerWrapper.validateHandlerClass(handlerClass) (via webSocket(...) registration paths) with a class that neither implements org.eclipse.jetty.websocket.api.WebSocketListener nor carries the spark.websocket.@WebSocket annotation.
Common situations: Developers create a plain POJO WebSocket handler and pass it to Spark's webSocket("/path", Handler.class) without implementing WebSocketListener or adding @WebSocket; migrations from other WebSocket libraries where the old handler class lacks the required interface/annotation; typos where the annotation imported is the wrong @WebSocket.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- path cannot be null or blank
- httpMethod cannot be null or blank
- WebSockets are only supported in the embedded server
- Could not instantiate websocket handler
- The must start with "/" and end with "/*". It's
AI-assisted analysis of perwendel/spark@1973e402f5 (2026-09-10).
Data as JSON: /api/errors/9af46c8aec7208e3.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/spark/embeddedserver/jetty/websocket/WebSocketHandlerWrapper.java:22
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
/**
* A wrapper for web socket handler classes/instances.
*/
public interface WebSocketHandlerWrapper {
/**
* Gets the actual handler - if necessary, instantiating an object.
*
* @return The handler instance.
*/
Object getHandler();
static void validateHandlerClass(Class<?> handlerClass) {
boolean valid = WebSocketListener.class.isAssignableFrom(handlerClass)
|| handlerClass.isAnnotationPresent(WebSocket.class);
if (!valid) {
throw new IllegalArgumentException(
"WebSocket handler must implement 'WebSocketListener' or be annotated as '@WebSocket'");
}
}
}
View on GitHub (pinned to 1973e402f5)