signalapp/Signal-Server · error
return Response.status(503).build();
Error message
return Response.status(503).build();
What it means
IOExceptionMapper maps generic java.io.IOExceptions to HTTP 503 Service Unavailable as a catch-all. Before that, it inspects the exception message: client-abort / broken-pipe style messages return 400, and Jetty 'Idle timeout ... elapsed' messages return 408 Request Timeout. The final 503 means an I/O failure occurred while handling the request and no more specific mapping applied.
Solutions
- Check server logs for the underlying IOException root cause (storage, network, dependency)
- Verify connectivity/health of external dependencies (attachment storage, database)
- If client disconnects are being misclassified as 503, extend the mapper's message patterns for that IO message
- Add retries with backoff on the client side; 503 here is effectively transient
Example fix
// before
code = response.code(); // 503 treated as permanent failure
// after
if (response.code() == 503) {
retryWithExponentialBackoff(request, maxAttempts = 3);
} Defensive patterns
Strategy: retry
Try / catch
if (response.code() == 503) {
retryWithExponentialBackoff(request, 3); // transient IO failure server-side
} else if (response.code() == 408 || response.code() == 400) {
reconnectAndResend(); // client abort/idle timeout
} Prevention
- Monitor server dependency health (storage, DB) to reduce spurious 503s
- Keep request uploads within idle-timeout windows
- Use client-side exponential backoff on 503
- Check server logs for the underlying IOException pattern
When it happens
Trigger: Any request handler throws an IOException not matching the early-EOF/idle-timeout patterns — e.g. failures reading request bodies, storage/attachment I/O errors, or downstream stream failures.
Common situations: Backend storage (S3/attachment service) connectivity problems; client disconnecting mid-upload causing IO variants not matched by the mapper's patterns; transient network faults between service and dependencies.
Related errors
- return Response.status(429).build();
- return Response.status(404).build();
- return Response.status(411)
- return Response.status(422).build();
- IOExceptionMapper
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/09c36686bf5a0a37.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/mappers/IOExceptionMapper.java:38
public Response toResponse(IOException e) {
if (!(e.getCause() instanceof java.util.concurrent.TimeoutException)) {
logger.warn("IOExceptionMapper", e);
} else {
// Some TimeoutExceptions are because the connection is idle, but are only distinguishable using the exception
// message
final String message = e.getCause().getMessage();
final boolean idleTimeout =
message != null &&
// org.eclipse.jetty.io.IdleTimeout
(message.startsWith("Idle timeout expired")
// org.eclipse.jetty.http2.HTTP2Session
|| (message.startsWith("Idle timeout") && message.endsWith("elapsed")));
if (idleTimeout) {
return Response.status(Response.Status.REQUEST_TIMEOUT).build();
}
}
return Response.status(503).build();
}
}
View on GitHub (pinned to 100ab61c82)