TooTallNate/Java-WebSocket · error · IOException
connection closed unexpectedly by peer
Error message
connection closed unexpectedly by peer
What it means
SSLSocketChannel2.read() throws this IOException during the TLS handshake (NEED_UNWRAP phase) when socketChannel.read() returns -1, meaning the underlying TCP connection was closed by the remote peer while encrypted handshake bytes were still expected. The library surfaces this instead of silently treating it as a normal close because the SSL handshake never completed, so the WebSocket connection state is ambiguous.
Solutions
- Check server TLS configuration (supported protocols/ciphers) and ensure the client's enabled protocols overlap (e.g. set system property https.protocols or SSLSocketFactory accordingly).
- Verify network path: if behind a proxy/firewall, confirm it allows the CONNECT/TLS traffic and does not time out mid-handshake.
- Confirm the target port actually serves TLS (wss://) and not plain ws://.
- Retry with reconnect/backoff and inspect server-side logs at the exact time of the failure to see why the peer closed.
- Update to a recent TooTallNate/Java-WebSocket version where handshake failures surface clearer error messages.
Example fix
// before: immediate connect without TLS config
WebSocketClient client = new WebSocketClient(uri);
// after: pin compatible TLS protocols before connecting
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(null, trustAllCerts, new SecureRandom());
client.setSocketFactory(ctx.getSocketFactory()); Defensive patterns
Strategy: retry
Validate before calling
// verify URI scheme and TLS support before connecting
if (!uri.getScheme().equals("wss")) throw new IllegalArgumentException("use wss://");
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(null, trustedCerts, new SecureRandom());
client.setSocketFactory(ctx.getSocketFactory()); Try / catch
try { client.connectBlocking(); } catch (IOException e) {
if (e.getMessage().contains("connection closed unexpectedly")) { scheduleReconnectWithBackoff(); }
} Prevention
- Align enabled TLS protocols/ciphers between client and server
- Confirm proxies/firewalls allow the TLS traffic and don't time out mid-handshake
- Use wss:// only against endpoints that actually serve TLS
- Add reconnect-with-backoff for transient network drops
When it happens
Trigger: The remote peer (or an intermediary like a proxy/load balancer) closes the TCP socket after the TLS ClientHello/ServerHello exchange begins but before the handshake finishes — e.g. server rejects the TLS version/cipher, a timeout fires mid-handshake, or the peer crashes.
Common situations: Connecting through a corporate proxy or firewall that drops TLS connections; server only supports TLS versions the client does not offer; port forwarded to a non-TLS service; aggressive idle timeouts on load balancers; server closing connections during TLS renegotiation.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- Connection is closed
- Handshake data rejected by client.
- rejected because of
- This websocket uses ws instead of wss. No SSLSession…
- Invalid status code received
AI-assisted analysis of TooTallNate/Java-WebSocket@afeacbf8c0 (2026-09-09).
Data as JSON: /api/errors/0483474f59d0fdc9.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/org/java_websocket/SSLSocketChannel2.java:175
while (it.hasNext()) {
Future<?> f = it.next();
if (f.isDone()) {
it.remove();
} else {
if (isBlocking()) {
consumeFutureUninterruptible(f);
}
return;
}
}
}
if (isReading && sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
if (!isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) {
inCrypt.compact();
int read = socketChannel.read(inCrypt);
if (read == -1) {
throw new IOException("connection closed unexpectedly by peer");
}
inCrypt.flip();
}
inData.compact();
unwrap();
if (readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) {
createBuffers(sslEngine.getSession());
return;
}
}
consumeDelegatedTasks();
if (tasks.isEmpty()
|| sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
socketChannel.write(wrap(emptybuffer));
if (writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) {
createBuffers(sslEngine.getSession());
return;
}View on GitHub (pinned to afeacbf8c0)