square/okhttp · error · IOException
Unexpected code
Error message
Unexpected code
What it means
Recipe-level guard after a GET that uses certificate pinning. The client is built with a CertificatePinner that pins publicobject.com to a single sha256 SPKI pin. Note: a pin MISMATCH does NOT reach this line — it fails earlier as SSLPeerUnverifiedException/SSLHandshakeException during the handshake. This 'Unexpected code' therefore means the TLS handshake succeeded (pin matched) but the HTTP response was non-2xx.
Source
Thrown at samples/guide/src/main/java/okhttp3/recipes/CertificatePinning.java:39
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public final class CertificatePinning {
private final OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(
new CertificatePinner.Builder()
.add("publicobject.com", "sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=")
.build())
.build();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/robots.txt")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
}
}
public static void main(String... args) throws Exception {
new CertificatePinning().run();
}
}
View on GitHub (pinned to 4fc0831380)
Solutions
- If you are seeing SSLPeerUnverifiedException, the pin is stale — recompute the pin by printing CertificatePinner.pin(certificate) for the current cert and update the CertificatePinner.
- If you are actually reaching this line, inspect response.code() — it is an ordinary HTTP error, not a pinning issue.
- Pin a backup hash (multiple .add() entries) so cert rotation does not break the app.
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()) {
// handshake already succeeded, so this is a plain HTTP error, not a pin failure
throw new IOException("HTTP " + response.code() + " (pin was OK)");
}
...
}
// and pin a backup so rotation does not break you:
// .add("publicobject.com", "sha256/<primary>")
// .add("publicobject.com", "sha256/<backup>") Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate that your pin set still matches the current cert
// Run this once to capture the current SPKI hashes, then pin them
try (Response r = new OkHttpClient().newCall(
new Request.Builder().url("https://publicobject.com/robots.txt").build()).execute()) {
for (Certificate c : r.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(c));
}
} Try / catch
try {
Response response = client.newCall(request).execute();
} catch (SSLPeerUnverifiedException | SSLHandshakeException e) {
// pin mismatch (or untrusted cert) — happens BEFORE the 'Unexpected code' line
throw new PinMismatchException("Certificate pin no longer matches", e);
} catch (IOException e) {
// reached the response guard: handshake was fine, this is an HTTP error
if (e.getMessage().startsWith("Unexpected code")) { /* plain HTTP error */ }
} Prevention
- Pin at least two SPKI hashes (primary + backup) so cert rotation does not break clients.
- Recognise that pin failures throw SSLHandshakeException earlier — the 'Unexpected code' line means the pin matched.
- Recompute pins after the server rotates its certificate.
- Avoid pinning leaf certs; prefer intermediate/SPKI pins.
When it happens
Trigger: GET https://publicobject.com/robots.txt with a pinned cert. Reaches this line only when the handshake passed and the server returned a non-2xx (e.g. 404 for /robots.txt, 503). If the pin were wrong you would get a different exception before execute() returned.
Common situations: Treating this error as a pin problem (it is not — pin failures throw earlier); the pinned cert rotated and now the handshake fails with SSLHandshakeException, which users misattribute to this line; /robots.txt genuinely missing.
Related errors
AI-assisted analysis of square/okhttp@4fc0831380 (2026-08-04).
Data as JSON: /data/errors/70c1829c73e7c085.json.
Report an issue: GitHub.