square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

The canonical OkHttp 'Synchronous Get' recipe: after `client.newCall(request).execute()`, the code calls `response.isSuccessful()` and throws `IOException("Unexpected code " + response)` for any non-2xx status before printing headers and body. This is the most minimal form of the idiom and the one most often copy-pasted.

Solutions

  1. Open the URL in a browser or with `curl -I` to confirm it still returns 200; if not, point the recipe at a URL you control.
  2. Replace the blanket throw with a switch on `response.code()` so 4xx is reported distinctly from 5xx and you can retry only 5xx/429.
  3. If the host is behind a proxy, configure `.proxy(...)` or `ProxySelector` on the `OkHttpClient` builder.
  4. For production code, distinguish transport errors (`IOException` from `execute()`) from HTTP-status errors (this throw) so they can be handled with different policies.

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()) {
  int code = response.code();
  if (code >= 200 && code < 300) {
    // success path: read headers + body
  } else if (code >= 500 || code == 429) {
    throw new IOException("Transient server error " + code + " for " + request.url());
  } else {
    throw new IOException("Client error " + code + " " + response.message()
        + " for " + request.url());
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: cheap reachability check (optional) so you fail before the call.
URI uri = URI.create("https://publicobject.com/helloworld.txt");
try {
  InetAddress.getAllByName(uri.getHost()); // throws if DNS dead
} catch (UnknownHostException e) {
  throw new IllegalStateException("Cannot resolve " + uri.getHost(), e);
}

// Post-call: classify instead of blanket-throw.
try (Response response = client.newCall(request).execute()) {
  int c = response.code();
  if (c >= 200 && c < 300) {
    // read headers + body
  } else if (c == 404) {
    throw new ContentMissingException(uri);
  } else if (c >= 500 || c == 429) {
    throw new TransientHttpException(c, response.header("Retry-After"));
  } else {
    throw new IOException("HTTP " + c + " " + response.message());
  }
}

Type guard

private static boolean isBodyReadable(Response r) {
  return r.isSuccessful() && r.body() != null;
}
private static boolean isTransient(int code) {
  return code == 429 || (code >= 500 && code < 600);
}

Try / catch

int attempts = 0;
while (true) {
  try (Response response = client.newCall(request).execute()) {
    if (response.isSuccessful()) {
      // read body
      break;
    }
    if (!isTransient(response.code())) {
      throw new IOException("Non-retryable HTTP " + response.code());
    }
    // transient: honor Retry-After if present, exponential backoff otherwise
    long backoff = Math.min(30_000L, 500L * (1L << attempts));
    Thread.sleep(backoff);
  } catch (IOException e) {
    // transport failure — also retryable on transient conditions
    if (++attempts > MAX_ATTEMPTS) throw e;
  }
}

Prevention

When it happens

Trigger: A GET to `https://publicobject.com/helloworld.txt` returns non-2xx: 404 if the path/file is moved or removed, 451/403 under geo or WAF blocks, 500/502/503 during origin outages, or 407 if a proxy demands authentication. DNS/TLS/connection failures throw a different exception (e.g. `UnknownHostException`, `SSLHandshakeException`) before this line is reached; the `Unexpected code` throw is purely an HTTP-status outcome.

Common situations: Hard-coded external sample URL going offline or being reorganized; running in a restricted network where the host is blocked; copy-pasting the recipe as production error handling and treating any 3xx-not-followed or 4xx as a crash; not realizing OkHttp follows redirects/30x by default so this line rarely fires for redirects.

Related errors


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

Appendix: source

Thrown at samples/guide/src/main/java/okhttp3/recipes/SynchronousGet.java:33

 */
package okhttp3.recipes;

import java.io.IOException;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public final class SynchronousGet {
  private final OkHttpClient client = new OkHttpClient();

  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);

      Headers responseHeaders = response.headers();
      for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
      }

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

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

View on GitHub (pinned to 91a8b34c6f)