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