square/okhttp · error · IOException

Unexpected code

Error message

Unexpected code 

What it means

Thrown in the Progress recipe: `if (!response.isSuccessful()) throw new IOException("Unexpected code " + response)`. A network interceptor wraps the response body in a ProgressResponseBody that reports bytes-read to a listener. The guard fires if the underlying server response is non-2xx. Note the progress body is still installed even on error responses, so the listener will fire for the error body bytes too.

Solutions

  1. Check response.code() before trusting the progress totals; an error body will produce misleading byte counts.
  2. Confirm the resource path still exists.
  3. Only forward to your ProgressListener when response.isSuccessful() if you only care about success bodies.
  4. Branch on the code instead of throwing so progress + status are both observable.

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()) {
    System.err.println("HTTP " + response.code() + " (progress totals were for the error body)");
    return;
  }
  System.out.println(response.body().string());
}
Defensive patterns

Strategy: validation

Validate before calling

// Only trust progress totals on success; check status first.
if (!response.isSuccessful()) { /* error body; ignore progress totals */ return; }

Type guard

static boolean progressReflectsTarget(Response r, long expectedLength) {
  return r.isSuccessful() && (r.body().contentLength() == -1 || r.body().contentLength() == expectedLength);
}

Try / catch

try (Response r = client.newCall(request).execute()) {
  if (!r.isSuccessful()) { /* status; progress listener reported error-body bytes */ return; }
} catch (IOException e) {
  // transport error
}

Prevention

When it happens

Trigger: GET https://publicobject.com/helloworld.txt returns 404/403/5xx; the ProgressResponseBody wraps whatever body the server returned (possibly an error page), so the listener reports bytes for the error body and then this line throws.

Common situations: Sample host removed helloworld.txt; the progress numbers look wrong because they are measuring an HTML error page rather than the expected file; network interceptor wrapping changing timing.

Related errors


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

Appendix: source

Thrown at samples/guide/src/main/java/okhttp3/recipes/Progress.java:72

          if (contentLength != -1) {
            System.out.format("%d%% done\n", (100 * bytesRead) / contentLength);
          }
        }
      }
    };

    OkHttpClient client = new OkHttpClient.Builder()
        .addNetworkInterceptor(chain -> {
          Response originalResponse = chain.proceed(chain.request());
          return originalResponse.newBuilder()
              .body(new ProgressResponseBody(originalResponse.body(), progressListener))
              .build();
        })
        .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 Progress().run();
  }

  private static class ProgressResponseBody extends ResponseBody {

    private final ResponseBody responseBody;
    private final ProgressListener progressListener;
    private BufferedSource bufferedSource;

    ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
      this.responseBody = responseBody;
      this.progressListener = progressListener;

View on GitHub (pinned to 91a8b34c6f)