square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

Recipe-level guard after a GET configured with a preemptive Basic auth interceptor (BasicAuthInterceptor adds Authorization: Basic <creds> for host publicobject.com before the request goes out). Because auth is sent on the first request (not after a 401 challenge), a wrong credential yields a final 401 that reaches this line, where java.io.IOException("Unexpected code " + response) is thrown.

Source

Thrown at samples/guide/src/main/java/okhttp3/recipes/PreemptiveAuth.java:41

import okhttp3.Response;

public final class PreemptiveAuth {
  private final OkHttpClient client;

  public PreemptiveAuth() {
    client = new OkHttpClient.Builder()
        .addInterceptor(
            new BasicAuthInterceptor("publicobject.com", "jesse", "password1"))
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/secrets/hellosecret.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 PreemptiveAuth().run();
  }

  static final class BasicAuthInterceptor implements Interceptor {
    private final String credentials;
    private final String host;

    BasicAuthInterceptor(String host, String username, String password) {
      this.credentials = Credentials.basic(username, password);
      this.host = host;
    }

View on GitHub (pinned to 4fc0831380)

Solutions

  1. Verify the credentials are valid for the realm.
  2. Make the host comparison robust: request.url().host().equalsIgnoreCase(host).
  3. Use https:// so the preemptive header is encrypted.
  4. Inspect response.code() to distinguish 401 (creds) from 403 (forbidden) from 404 (missing).

Example fix

// before
if (request.url().host().equals(host)) {
  request = request.newBuilder().header("Authorization", credentials).build();
}
...
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

// after
if (request.url().host().equalsIgnoreCase(host)) {
  request = request.newBuilder().header("Authorization", credentials).build();
}
...
if (response.code() == 401) {
  throw new IOException("Preemptive auth rejected for " + request.url());
}
if (!response.isSuccessful()) {
  throw new IOException("HTTP " + response.code() + " for " + request.url());
}
Defensive patterns

Strategy: validation

Validate before calling

// Make host matching robust and verify credentials before relying on the request
// inside the interceptor:
if (request.url().host().equalsIgnoreCase(host)) {
  request = request.newBuilder().header("Authorization", credentials).build();
}
// before calling, sanity-check:
if (username == null || password == null) throw new IllegalArgumentException("creds required");
// prefer https so the header is encrypted

Try / catch

try (Response response = client.newCall(request).execute()) {
  if (response.code() == 401) throw new AuthException("Preemptive Basic rejected for " + request.url());
  if (response.code() == 403) throw new ForbiddenException(request.url().toString());
  if (!response.isSuccessful()) throw new HttpException(response.code(), response.message());
}

Prevention

When it happens

Trigger: GET https://publicobject.com/secrets/hellosecret.txt with an always-on Authorization header. Fires on 401 (wrong username/password), 403 (authenticated but forbidden), or 404 (resource gone). Because the interceptor only injects the header when request.url().host() equals 'publicobject.com', a host mismatch silently sends no auth and you can still get 401.

Common situations: Placeholder credentials 'jesse/password1' do not match the target; host check is case-sensitive — a URL with uppercase host or a trailing-dot host (publicobject.com.) will not match and auth is omitted; sending Basic over http leaks credentials.

Related errors


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