square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

Recipe-level guard inside a client built with a custom trust store (HandshakeCertificates trusting only Comodo RSA, Entrust Root, and Let's Encrypt X3 roots). The recipe first dumps response headers, then throws java.io.IOException("Unexpected code " + response) for non-2xx. Important: if the server's chain does NOT trace to one of the three trusted roots, you never reach this line — you get SSLHandshakeException (cert path validation failure) earlier. Reaching this line means trust succeeded and the response is an ordinary non-2xx.

Source

Thrown at samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java:157

    client = new OkHttpClient.Builder()
            .sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
            .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()) {
        Headers responseHeaders = response.headers();
        for (int i = 0; i < responseHeaders.size(); i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }

        throw new IOException("Unexpected code " + response);
      }

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

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

View on GitHub (pinned to 4fc0831380)

Solutions

  1. If you see SSLHandshakeException, uncomment .addPlatformTrustedCertificates() or add the missing root CA.
  2. If you actually reach this line, handle response.code() as a normal HTTP error.
  3. For production, load your trust anchors from a bundled PEM resource rather than hard-coding three CAs.

Example fix

// before
if (!response.isSuccessful()) {
  // prints headers first, then throws
  throw new IOException("Unexpected code " + response);
}

// after
if (!response.isSuccessful()) {
  throw new IOException("HTTP " + response.code()
      + " (trust was OK; non-2xx from origin)");
}
// and if you are seeing handshake errors instead, enable platform trust:
//   new HandshakeCertificates.Builder()
//       .addPlatformTrustedCertificates()
//       .addTrustedCertificate(myOrgRoot)
//       .build();
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate that your trust store covers the target's chain
// (otherwise you will get SSLHandshakeException, not the 'Unexpected code' line)
// Use openssl s_client -connect host:443 -showcerts to see the chain issuer roots
// and ensure each root is in your HandshakeCertificates.Builder.

Try / catch

try {
  Response response = client.newCall(request).execute();
  if (!response.isSuccessful()) throw new HttpException(response.code(), response.message());
} catch (SSLHandshakeException e) {
  // chain does not trace to one of the three trusted roots
  throw new TrustException("Untrusted chain — add the missing root or enable platform trust", e);
}

Prevention

When it happens

Trigger: GET https://publicobject.com/helloworld.txt with the three-CA trust store. Reaches this line when the cert chain IS trusted but the response is 4xx/5xx. If the chain were untrusted (e.g. site moved to a Google/AWS cert) the call fails at handshake with 'unable to find valid certification path to requested target'.

Common situations: Commenting out .addPlatformTrustedCertificates() (as shipped) then trying to reach sites whose chain is not one of the three CAs — that is a handshake failure, not this error; site legitimately returns 404/503; cert rotated to a chain outside the three.

Related errors


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