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

  1. 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).
  2. Verify network path: if behind a proxy/firewall, confirm it allows the CONNECT/TLS traffic and does not time out mid-handshake.
  3. Confirm the target port actually serves TLS (wss://) and not plain ws://.
  4. Retry with reconnect/backoff and inspect server-side logs at the exact time of the failure to see why the peer closed.
  5. 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

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

Related errors


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)