square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

OkHttp's Response.isSuccessful() returns true only when the HTTP status code is in [200, 300). The recipe at RequestBodyCompression.java:59 wraps any non-2xx response in an IOException with the literal message 'Unexpected code ' concatenated with the Response's toString(). This is a sample-recipe convention, not a dedicated OkHttp exception type, so the stack trace is the only signal of which upstream API actually rejected the call.

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 4fc0831380)

Solutions

  1. Replace the Google URL Shortener URL with a live endpoint (e.g. Firebase Dynamic Links) or a local mock server that returns 200, since the URL Shortener API is fully shut down.
  2. Inspect the failing Response before throwing — log response.code(), response.headers(), and response.body().string() — to identify 401/403/404/415 precisely.
  3. If keeping GzipRequestInterceptor, confirm the upstream server supports Content-Encoding: gzip on requests, otherwise drop the interceptor or gate it on a per-host condition.
  4. Obtain a fresh Google API key from https://console.developers.google.com/project and enable the relevant API for it; never ship the sample key 'AIzaSyAx2WZYe0My0My0i-uGurpvraYJxO7XNbwiGs'.

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.peekBody(2048).string();
    throw new IOException("Unexpected code " + response.code()
        + " for " + request.url() + ": " + body);
  }
  System.out.println(response.body().string());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the endpoint is alive and the key is set before the call
String apiKey = System.getenv("GOOGLE_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
  throw new IllegalStateException("GOOGLE_API_KEY env var not set");
}
// Prefer a live endpoint; the URL Shortener API is shut down.
if (request.url().toString().contains("urlshortener/v1/url")) {
  throw new IllegalStateException("URL Shortener API is deprecated; switch endpoint");
}

Try / catch

try (Response response = client.newCall(request).execute()) {
  if (!response.isSuccessful()) {
    throw new IOException("HTTP " + response.code() + " "
        + response.peekBody(2048).string());
  }
  handle(response.body().string());
} catch (IOException e) {
  // distinguish network failure (no response) from non-2xx (has code)
  throw new RuntimeException("URL shortener call failed: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: Posting JSON to https://www.googleapis.com/urlshortener/v1/url?key=<key> after Google retired the URL Shortener API (March 2018) — server returns 404/410/403. Also triggered when the GzipRequestInterceptor (RequestBodyCompression.java:70-82) sets Content-Encoding: gzip and Google's front end refuses the gzipped request body with a 400/415. A revoked/invalid GOOGLE_API_KEY (hardcoded at line 38) yields 403 Forbidden.

Common situations: Running the bundled OkHttp samples verbatim against a now-deprecated Google endpoint; copying the recipe's hardcoded API key into production; enabling client-side request-body gzip against a server that does not advertise gzip request-encoding support; switching from http to a vhost that returns a non-2xx error page.

Related errors


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