square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

Thrown in the custom-trust-store recipe: `throw new IOException("Unexpected code " + response)`. Unlike the other recipes, this one first prints all response headers THEN throws. The client trusts only the three embedded PEM root CAs (Comodo, Entrust, Let's Encrypt). If the server's chain is NOT anchored at one of those roots, you get an SSLPeerUnverifiedException during the handshake (earlier, different error). This IOException only fires when TLS succeeded but the HTTP status is non-2xx.

Solutions

  1. Check response.code() (printed headers in the sample help diagnose).
  2. If you actually need broad HTTPS access, uncomment .addPlatformTrustedCertificates() in the builder.
  3. Add the specific root CA for any host you want to reach that is not covered by the three embedded certs.
  4. Distinguish SSLPeerUnverifiedException (wrong/missing trust root) from this HTTP-status IOException.

Example fix

// before
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);
}

// after
if (!response.isSuccessful()) {
  throw new IOException("HTTP " + response.code() + " " + response.message());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the target host's root CA is in the custom trust store before calling.
// For broad access, include platform roots.
HandshakeCertificates certs = new HandshakeCertificates.Builder()
    .addPlatformTrustedCertificates() // uncomment for general HTTPS
    .build();

Type guard

static boolean trustedBy(HandshakeCertificates certs, X509Certificate serverRoot) {
  return Arrays.asList(certs.trustManager().getAcceptedIssuers()).contains(serverRoot);
}

Try / catch

try {
  // call
} catch (SSLPeerUnverifiedException e) {
  // server chain not anchored at one of your 3 embedded roots -> add the missing root
} catch (IOException e) {
  // includes 'Unexpected code' (HTTP status) when TLS succeeded
}

Prevention

When it happens

Trigger: https://publicobject.com/helloworld.txt presented a chain the custom trust manager accepted (Comodo/Entrust/Let's Encrypt root), but the server returned 404/403/5xx. Commenting out addPlatformTrustedCertificates means most OTHER sites will fail at the TLS layer instead.

Common situations: Sample host removed the file; pointing the sample at a host whose CA is not in the three embedded roots (-> SSL error, not this); forgetting to uncomment addPlatformTrustedCertificates when you need general HTTPS access.

Related errors


AI-assisted analysis of square/okhttp@91a8b34c6f (2026-08-10). Data as JSON: /api/errors/0bcc1be7d4c0e31f. Report an issue: GitHub.

Appendix: 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 91a8b34c6f)