square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

Recipe-level guard after a GET over a client configured with a custom ConnectionSpec that restricts cipher suites to four ECDHE AEAD suites and a DelegatingSSLSocketFactory that forces those suites on each socket. Reaching this line means the TLS handshake negotiated one of the configured suites; if the HTTP response is then non-2xx, java.io.IOException("Unexpected code " + response) is thrown. A cipher-suite negotiation FAILURE surfaces earlier as SSLHandshakeException, not here.

Source

Thrown at samples/guide/src/main/java/okhttp3/recipes/CustomCipherSuites.java:161

    @Override public Socket createSocket(
        InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
      return configureSocket((SSLSocket) delegate.createSocket(
          address, port, localAddress, localPort));
    }

    protected SSLSocket configureSocket(SSLSocket socket) throws IOException {
      return socket;
    }
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.handshake().cipherSuite());
      System.out.println(response.body().string());
    }
  }

  public static void main(String... args) throws Exception {
    new CustomCipherSuites().run();
  }
}

View on GitHub (pinned to 4fc0831380)

Solutions

  1. Inspect response.handshake().cipherSuite() — if it is one of the four configured, the handshake is fine and this is a plain HTTP error.
  2. If you are getting SSLHandshakeException instead, broaden the ConnectionSpec or fall back to ConnectionSpec.MODERN_TLS.
  3. Log response.code() to separate HTTP errors from cipher/TLS errors.

Example fix

// before
try (Response response = client.newCall(request).execute()) {
  if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  ...
}

// after
try (Response response = client.newCall(request).execute()) {
  if (!response.isSuccessful()) {
    throw new IOException("HTTP " + response.code()
        + " (negotiated cipher " + response.handshake().cipherSuite() + ")");
  }
  ...
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the server supports at least one of your configured suites before relying on it
// (e.g. with sslscan or openssl s_client -connect host:443 -cipher 'ECDHE-ECDSA-AES128-GCM-SHA256')
// In code, broaden the spec as a fallback if negotiation fails.

Try / catch

try {
  Response response = client.newCall(request).execute();
  if (!response.isSuccessful()) throw new HttpException(response.code(), response.message());
} catch (SSLHandshakeException e) {
  // No overlap between your 4 configured suites and the server
  throw new TlsConfigException("Server supports none of the configured cipher suites", e);
} catch (IOException e) {
  if (e.getMessage() != null && e.getMessage().startsWith("Unexpected code")) {
    // handshake succeeded; this is a plain HTTP error
  }
  throw e;
}

Prevention

When it happens

Trigger: GET https://publicobject.com/helloworld.txt through the customised client. Reaches this line on a non-2xx after a successful handshake; surfaces when the server lacks overlap with the four configured suites (but that throws earlier), or when the origin returns 4xx/5xx.

Common situations: Over-restricting cipher suites so older server stacks cannot negotiate (would surface as handshake failure, not this); assuming this exception is about ciphers when it is actually an HTTP error; server-side 5xx during maintenance.

Related errors


AI-assisted analysis of square/okhttp@4fc0831380 (2026-08-04). Data as JSON: /data/errors/ee0bae5c7c6c62a5.json. Report an issue: GitHub.