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

  1. Distinguish the two throws in this file: line 38 is the security denylist, line 55 is plain HTTP status.
  2. Inspect response.code() to identify the real HTTP problem.
  3. Confirm the resource path still exists.
  4. 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

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)