square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

This is the OkHttp recipe idiom for turning a non-2xx HTTP response into a checked exception: after `client.newCall(request).execute()` returns, the code checks `response.isSuccessful()` (status in 200-299) and throws `new IOException("Unexpected code " + response)` if not. OkHttp itself does not throw on HTTP error status (it only throws on network/protocol failures); the throw is application-level and the `response` object's toString() (code, message, url) is appended to give context. In RequestBodyCompression.java:59 it fires after a gzipped POST to the Google URL Shortener API.

Solutions

  1. Confirm the URL Shortener API is gone and switch to a live endpoint (e.g. Firebase Dynamic Links shortLinks API, or tinyURL/is.gd) — the sample as written can no longer return 200.
  2. Register and use your own API key; never rely on the sample `GOOGLE_API_KEY` constant which is revoked.
  3. Inspect `response.code()`, `response.message()`, and `response.body().string()` instead of throwing — they tell you whether it is auth (401/403), gone (410/404), or media-type (415).
  4. If migrating to a server that does not support gzip, remove `.addInterceptor(new GzipRequestInterceptor())` or gate it on the target host.

Example fix

// before
try (Response response = client.newCall(request).execute()) {
  if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  System.out.println(response.body().string());
}

// after
try (Response response = client.newCall(request).execute()) {
  if (!response.isSuccessful()) {
    String body = response.body() != null ? response.body().string() : "<no body>";
    throw new IOException("HTTP " + response.code() + " " + response.message()
        + " for " + response.request().url() + ": " + body);
  }
  System.out.println(response.body().string());
}
Defensive patterns

Strategy: validation

Validate before calling

// Before issuing the call: verify endpoint is reachable and key configured.
String key = System.getenv("GOOGLE_API_KEY");
if (key == null || key.isBlank()) {
  throw new IllegalStateException("GOOGLE_API_KEY env var not set; cannot call API.");
}
// Don't gzip to hosts that reject it.
boolean targetAcceptsGzip = Set.of("my-shortener.example", "is.gd").contains(host);
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(targetAcceptsGzip ? new GzipRequestInterceptor() : chain -> chain.proceed(chain.request()))
    .build();

// After execute(), branch on code instead of throwing blindly:
try (Response response = client.newCall(request).execute()) {
  switch (response.code() / 100) {
    case 2 -> System.out.println(response.body().string());
    case 4 -> handleClientError(response);
    case 5 -> throw new IOException("Server error " + response.code());
    default -> throw new IOException("Unexpected status " + response.code());
  }
}

Type guard

// Java has no structural 'type guard'; emulate narrowing with a small helper.
/** @return true iff response represents a success you can safely read the body of. */
private static boolean isUsable(@NonNull Response r) {
  int c = r.code();
  return c >= 200 && c < 300 && r.body() != null;
}
// Use: if (isUsable(response)) { ... } else { log(response.code(), response.message()); }

Try / catch

try (Response response = client.newCall(request).execute()) {
  if (!response.isSuccessful()) {
    String detail = response.body() != null ? response.body().string() : "<no body>";
    if (response.code() == 403 || response.code() == 404 || response.code() == 410) {
      throw new ApiDeprecatedException("Google URL Shortener API unavailable (" + response.code() + "): " + detail);
    }
    throw new IOException("HTTP " + response.code() + " " + response.message() + ": " + detail);
  }
  System.out.println(response.body().string());
} catch (IOException e) {
  // transport-level (DNS, TLS, timeout) — distinct from the status-level throw above
  throw new RuntimeException("Transport failure calling Google API", e);
}

Prevention

When it happens

Trigger: POSTing JSON to `https://www.googleapis.com/urlshortener/v1/url?key=AIzaSy...` returns a non-2xx status — most commonly 403 (Google turned down the URL Shortener API in March 2018 and disabled legacy keys), 404/410 (endpoint removed), or 415 if the GzipRequestInterceptor's `Content-Encoding: gzip` is not accepted by the server. Any of these makes `response.isSuccessful()` false at line 59.

Common situations: Running the sample unchanged years after Google deprecated the API; copying the recipe verbatim and never rotating the hardcoded `GOOGLE_API_KEY`; pointing the recipe at a server that rejects gzip-compressed request bodies (the interceptor is explicitly documented as 'Many webservers can't handle this!'); corporate proxies rewriting/compressing bodies and confusing the server.

Related errors


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

Appendix: source

Thrown at samples/guide/src/main/java/okhttp3/recipes/RequestBodyCompression.java:59

  private final OkHttpClient client = new OkHttpClient.Builder()
      .addInterceptor(new GzipRequestInterceptor())
      .build();
  private final Moshi moshi = new Moshi.Builder().build();
  private final JsonAdapter<Map<String, String>> mapJsonAdapter = moshi.adapter(
      Types.newParameterizedType(Map.class, String.class, String.class));

  public void run() throws Exception {
    Map<String, String> requestBody = new LinkedHashMap<>();
    requestBody.put("longUrl", "https://publicobject.com/2014/12/04/html-formatting-javadocs/");
    RequestBody jsonRequestBody = RequestBody.create(
        mapJsonAdapter.toJson(requestBody), MEDIA_TYPE_JSON);
    Request request = new Request.Builder()
        .url("https://www.googleapis.com/urlshortener/v1/url?key=" + GOOGLE_API_KEY)
        .post(jsonRequestBody)
        .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 RequestBodyCompression().run();
  }

  /** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
  static class GzipRequestInterceptor implements Interceptor {
    @Override public Response intercept(Chain chain) throws IOException {
      Request originalRequest = chain.request();
      if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
        return chain.proceed(originalRequest);
      }

      Request compressedRequest = originalRequest.newBuilder()

View on GitHub (pinned to 91a8b34c6f)