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

  1. 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.
  2. Fix the underlying exception thrown by the resource method handling that verb/path.
  3. 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.
  4. 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

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


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)