signalapp/Signal-Server · error
sendErrorResponse(requestMessage…
Error message
sendErrorResponse(requestMessage, Response.status(500).build());
What it means
When a WebSocket request handled through the Jersey resource provider fails asynchronously, the provider logs the failure and sends a synthetic HTTP 500 response back over the WebSocket. This is a fallback path: the request itself threw, so the server can only report a generic internal error without details. The 500 is accompanied by a WARN log containing the exception that actually caused the failure.
Solutions
- Inspect the server WARN log line 'Websocket Error: <verb> <path>' for the full stack trace of the root cause — the 500 itself carries no detail.
- Fix the underlying exception thrown by the resource method handling that verb/path.
- Register a javax.ws.rs.ext.ExceptionMapper for the exception type so it maps to a meaningful status instead of falling through to the 500 fallback.
- Check the client sent a valid request body/verb/path matching a registered resource.
Example fix
// before: resource method lets NPE propagate -> generic 500 over websocket
public Response handle(Request req) { return db.lookup(req.getId()).get(); }
// after: map expected failure to a proper status
public Response handle(Request req) {
return db.lookup(req.getId())
.map(Response::ok)
.orElse(Response.status(404).build())
.build();
} Defensive patterns
Strategy: try-catch
Try / catch
client.onMessage((ws, bytes) -> {
try {
sendRequest(bytes);
} catch (Exception e) {
logger.warn("websocket request failed", e); // server logs 'Websocket Error: ...' with the real cause
}
}); Prevention
- Register ExceptionMappers for all domain exceptions so failures map to meaningful statuses instead of the 500 fallback.
- Monitor WARN logs for 'Websocket Error: verb path' to catch resource bugs early.
- Validate request bodies/paths client-side before sending over the socket.
When it happens
Trigger: Any completion-stage exception raised while dispatching a WebSocket request through WebSocketResourceProvider.handleRequest — e.g. a Jersey/resource method throws an unhandled exception, serialization of the response fails, or an internal resource error occurs during onWebSocketBinary processing.
Common situations: Bugs in a WebSocket-exposed resource method (NPEs, DB failures), broken request bodies that blow up mid-processing, or downstream service exceptions not mapped by exception mappers, surfacing to the client as an opaque 500 over the socket.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- interrupted during delivery
- delivery cancelled
- failure during delivery
- could not parse already validated number
- 500 Internal Server Error (registration service failure)
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/cfd97c5cb5ba8d5b.
Report an issue: GitHub.
Appendix: source
Thrown at websocket-resources/src/main/java/org/whispersystems/websocket/WebSocketResourceProvider.java:238
containerRequest, responseBody);
responseFuture
.thenAccept(response -> {
try {
final int responseBytes = responseBody.size();
containerRequest.setProperty(RESPONSE_LENGTH_PROPERTY, responseBytes);
sendResponse(requestMessage, response, responseBody);
} catch (IOException e) {
throw new RuntimeException(e);
}
requestLog.log(remoteAddress, containerRequest, response);
})
.exceptionally(exception -> {
logger.warn("Websocket Error: " + requestMessage.getVerb() + " " + requestMessage.getPath() + "\n"
+ requestMessage.getBody(), exception);
try {
containerRequest.setProperty(RESPONSE_LENGTH_PROPERTY, 0);
sendErrorResponse(requestMessage, Response.status(500).build());
} catch (IOException e) {
logger.warn("Failed to send error response", e);
}
requestLog.log(remoteAddress, containerRequest,
new ContainerResponse(containerRequest, Response.status(500).build()));
return null;
});
}
private static Map<String, List<String>> toLowerCaseHeaders(Map<String, List<String>> headers) {
return headers.entrySet().stream().collect(Collectors.toMap(
entry -> entry.getKey().trim().toLowerCase(),
Map.Entry::getValue));
}
@VisibleForTesting
static Map<String, List<String>> getCombinedHeaders(
final Map<String, List<String>> lowerCaseUpgradeRequestHeaders,View on GitHub (pinned to 100ab61c82)