{"record":{"id":"ebc3523e61c72ff2","repo":"square/okhttp","slug":"unexpected-code-ebc352","errorCode":null,"errorMessage":"Unexpected code ","messagePattern":"Unexpected code ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"samples/guide/src/main/java/okhttp3/recipes/RequestBodyCompression.java","lineNumber":59,"sourceCode":"  private final OkHttpClient client = new OkHttpClient.Builder()\n      .addInterceptor(new GzipRequestInterceptor())\n      .build();\n  private final Moshi moshi = new Moshi.Builder().build();\n  private final JsonAdapter<Map<String, String>> mapJsonAdapter = moshi.adapter(\n      Types.newParameterizedType(Map.class, String.class, String.class));\n\n  public void run() throws Exception {\n    Map<String, String> requestBody = new LinkedHashMap<>();\n    requestBody.put(\"longUrl\", \"https://publicobject.com/2014/12/04/html-formatting-javadocs/\");\n    RequestBody jsonRequestBody = RequestBody.create(\n        mapJsonAdapter.toJson(requestBody), MEDIA_TYPE_JSON);\n    Request request = new Request.Builder()\n        .url(\"https://www.googleapis.com/urlshortener/v1/url?key=\" + GOOGLE_API_KEY)\n        .post(jsonRequestBody)\n        .build();\n\n    try (Response response = client.newCall(request).execute()) {\n      if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\n\n      System.out.println(response.body().string());\n    }\n  }\n\n  public static void main(String... args) throws Exception {\n    new RequestBodyCompression().run();\n  }\n\n  /** This interceptor compresses the HTTP request body. Many webservers can't handle this! */\n  static class GzipRequestInterceptor implements Interceptor {\n    @Override public Response intercept(Chain chain) throws IOException {\n      Request originalRequest = chain.request();\n      if (originalRequest.body() == null || originalRequest.header(\"Content-Encoding\") != null) {\n        return chain.proceed(originalRequest);\n      }\n\n      Request compressedRequest = originalRequest.newBuilder()","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/square/okhttp/blob/91a8b34c6f44bd28c421364f8edadc9f324dddd9/samples/guide/src/main/java/okhttp3/recipes/RequestBodyCompression.java#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Register and use your own API key; never rely on the sample `GOOGLE_API_KEY` constant which is revoked.","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).","If migrating to a server that does not support gzip, remove `.addInterceptor(new GzipRequestInterceptor())` or gate it on the target host."],"exampleFix":"// before\ntry (Response response = client.newCall(request).execute()) {\n  if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\n  System.out.println(response.body().string());\n}\n\n// after\ntry (Response response = client.newCall(request).execute()) {\n  if (!response.isSuccessful()) {\n    String body = response.body() != null ? response.body().string() : \"<no body>\";\n    throw new IOException(\"HTTP \" + response.code() + \" \" + response.message()\n        + \" for \" + response.request().url() + \": \" + body);\n  }\n  System.out.println(response.body().string());\n}","handlingStrategy":"validation","validationCode":"// Before issuing the call: verify endpoint is reachable and key configured.\nString key = System.getenv(\"GOOGLE_API_KEY\");\nif (key == null || key.isBlank()) {\n  throw new IllegalStateException(\"GOOGLE_API_KEY env var not set; cannot call API.\");\n}\n// Don't gzip to hosts that reject it.\nboolean targetAcceptsGzip = Set.of(\"my-shortener.example\", \"is.gd\").contains(host);\nOkHttpClient client = new OkHttpClient.Builder()\n    .addInterceptor(targetAcceptsGzip ? new GzipRequestInterceptor() : chain -> chain.proceed(chain.request()))\n    .build();\n\n// After execute(), branch on code instead of throwing blindly:\ntry (Response response = client.newCall(request).execute()) {\n  switch (response.code() / 100) {\n    case 2 -> System.out.println(response.body().string());\n    case 4 -> handleClientError(response);\n    case 5 -> throw new IOException(\"Server error \" + response.code());\n    default -> throw new IOException(\"Unexpected status \" + response.code());\n  }\n}","typeGuard":"// Java has no structural 'type guard'; emulate narrowing with a small helper.\n/** @return true iff response represents a success you can safely read the body of. */\nprivate static boolean isUsable(@NonNull Response r) {\n  int c = r.code();\n  return c >= 200 && c < 300 && r.body() != null;\n}\n// Use: if (isUsable(response)) { ... } else { log(response.code(), response.message()); }","tryCatchPattern":"try (Response response = client.newCall(request).execute()) {\n  if (!response.isSuccessful()) {\n    String detail = response.body() != null ? response.body().string() : \"<no body>\";\n    if (response.code() == 403 || response.code() == 404 || response.code() == 410) {\n      throw new ApiDeprecatedException(\"Google URL Shortener API unavailable (\" + response.code() + \"): \" + detail);\n    }\n    throw new IOException(\"HTTP \" + response.code() + \" \" + response.message() + \": \" + detail);\n  }\n  System.out.println(response.body().string());\n} catch (IOException e) {\n  // transport-level (DNS, TLS, timeout) — distinct from the status-level throw above\n  throw new RuntimeException(\"Transport failure calling Google API\", e);\n}","preventionTips":["Treat sample API keys and URLs as expired-on-publish; never ship them.","Read the response code and body before throwing so the error message is diagnostic.","If migrating endpoints, confirm the new server accepts `Content-Encoding: gzip` before enabling the GzipRequestInterceptor.","Distinguish transport IOException (no response) from HTTP-status logic (response present, non-2xx) in your catch blocks."],"tags":["okhttp","java","http-status","api-deprecation","gzip"],"backgroundTag":null,"analyzedSha":"91a8b34c6f44bd28c421364f8edadc9f324dddd9","analyzedAt":"2026-08-10T18:39:54.316Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}