square/okhttp · error · IOException
Unexpected code
Error message
Unexpected code
What it means
Thrown in the CheckHandshake recipe AFTER the denylist interceptor already ran: `if (!response.isSuccessful()) throw new IOException("Unexpected code " + response)`. The handshake interceptor only rejects denylisted certs; for everything else it calls chain.proceed(). This line then fires if the server returned a non-2xx HTTP status on a handshake that was allowed through.
Solutions
- Distinguish the two throws in this file: line 38 is the security denylist, line 55 is plain HTTP status.
- Inspect response.code() to identify the real HTTP problem.
- Confirm the resource path still exists.
- Branch on the code instead of throwing to keep the demo's diagnostics usable.
Example fix
// before
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
// after
if (!response.isSuccessful()) {
throw new IOException("HTTP " + response.code() + " (handshake passed denylist)");
} Defensive patterns
Strategy: validation
Validate before calling
// Distinguish the two throws in this file by code/message.
try (Response r = client.newCall(request).execute()) {
if (!r.isSuccessful()) { /* HTTP status only; denylist would have thrown earlier */ return; }
} catch (IOException e) {
if (e.getMessage().contains("Denylisted")) { /* security event */ }
} Type guard
static boolean handshakePassedDenylist(IOException e) { return e.getMessage() == null || !e.getMessage().startsWith("Denylisted"); } Try / catch
try {
// call
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Denylisted peer certificate")) {
// line 38 security event
} else if (e.getMessage() != null && e.getMessage().startsWith("Unexpected code")) {
// line 55 HTTP status
}
} Prevention
- Tell apart line 38 (denylist) from line 55 (HTTP status) by the exception message.
- Inspect response.code() to identify the HTTP problem.
- Keep the denylist current so legitimate requests are not blocked.
- Branch on code instead of throwing for clearer HTTP diagnostics.
When it happens
Trigger: The handshake interceptor did not block the cert (pin not in denylist), chain.proceed() completed, but https://publicobject.com/helloworld.txt returned 404/403/5xx.
Common situations: Sample host removed helloworld.txt; server-side error; confusing a denylist rejection (which throws earlier at line 38) with this HTTP-status throw.
Related errors
AI-assisted analysis of square/okhttp@91a8b34c6f (2026-08-10).
Data as JSON: /api/errors/5fd4a849cb433425.
Report an issue: GitHub.
Appendix: source
Thrown at samples/guide/src/main/java/okhttp3/recipes/CheckHandshake.java:55
if (denylist.contains(pin)) {
throw new IOException("Denylisted peer certificate: " + pin);
}
}
return chain.proceed(chain.request());
}
};
private final OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(CHECK_HANDSHAKE_INTERCEPTOR)
.build();
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.body().string());
}
}
public static void main(String... args) throws Exception {
new CheckHandshake().run();
}
}
View on GitHub (pinned to 91a8b34c6f)