square/okhttp · error · IOException
Unexpected code
Error message
Unexpected code
What it means
Thrown in the custom-cipher-suites recipe after a successful TLS handshake: `if (!response.isSuccessful()) throw new IOException("Unexpected code " + response)`. The custom ConnectionSpec restricts TLS to a handful of ECDHE cipher suites; if the server supports one of them the handshake succeeds and this line fires only on a non-2xx HTTP status. If none of the configured suites match, you get an SSLHandshakeException earlier, not this line.
Solutions
- Distinguish TLS handshake errors from HTTP status errors; this line means TLS worked.
- Inspect response.code() for the real HTTP status.
- If you also see handshake errors elsewhere, widen the ConnectionSpec cipher list.
- Branch on the code instead of throwing for clearer diagnostics.
Example fix
// before
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
// after
if (!response.isSuccessful()) {
throw new IOException("HTTP " + response.code()
+ " (cipher=" + response.handshake().cipherSuite() + ")");
} Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm at least one configured cipher is server-supported before relying on this client.
// (Best done by a smoke-test request; static checks of cipher names are fragile.)
try (Response r = client.newCall(smokeTestRequest).execute()) {
if (!r.isSuccessful()) { /* HTTP status; TLS worked */ }
} Type guard
static boolean handshakeUsedCustomSuite(Response r, List<CipherSuite> allowed) {
return r.handshake() != null && allowed.contains(r.handshake().cipherSuite());
} Try / catch
try {
// call
} catch (SSLHandshakeException e) {
// no common cipher suite with the server -> widen ConnectionSpec
} catch (IOException e) {
// includes 'Unexpected code' (HTTP status) once TLS succeeded
} Prevention
- Distinguish SSLHandshakeException (cipher/TLS mismatch) from HTTP-status IOException.
- Do not over-restrict cipher suites; most apps should leave defaults.
- Inspect response.handshake().cipherSuite() to confirm negotiation.
- Branch on code rather than throwing to keep HTTP errors visible.
When it happens
Trigger: TLS handshake succeeded with one of the four configured suites against https://publicobject.com/helloworld.txt, but the server returned 404/403/5xx. (A cipher-suite mismatch would surface as SSLHandshakeException during connect, before any HTTP status exists.)
Common situations: Sample host removed the file; confusing a handshake failure (no common cipher) with this HTTP-status throw; restricting cipher suites so tightly that some servers cannot connect (but that is a different error).
Related errors
AI-assisted analysis of square/okhttp@91a8b34c6f (2026-08-10).
Data as JSON: /api/errors/ee0bae5c7c6c62a5.
Report an issue: GitHub.
Appendix: 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 91a8b34c6f)