square/okhttp · error · IOException
Unexpected code
Error message
Unexpected code
What it means
Same idiom, this time in the UploadProgress recipe after uploading a PNG to Imgur: `if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);`. The `response` object's toString carries the Imgur error code/message. The wrapping `ProgressRequestBody` only instruments bytes written; it does not change success semantics.
Solutions
- Register at https://api.imgur.com/oauth2/addclient and replace `IMGUR_CLIENT_ID` with your own; for user-scoped uploads use OAuth2 `Bearer <access_token>` instead of `Client-ID`.
- Verify the upload file exists and is a valid PNG before building the request (guard against empty/malformed body that yields 400).
- Before throwing, read and log `response.body().string()` and `response.code()` so you can tell 401 (auth) from 429 (rate) from 400 (payload).
- Respect `X-RateLimit-ClientRemaining`/`X-RateLimit-UserRemaining` headers and back off on 429.
Example fix
// before
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()) {
String body = response.body() != null ? response.body().string() : "";
if (!response.isSuccessful()) {
throw new IOException("Imgur upload failed: HTTP " + response.code()
+ " (rateLimit-client=" + response.header("X-RateLimit-ClientRemaining") + "): " + body);
}
System.out.println(body);
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: validate credentials and file before the upload.
String clientId = System.getenv("IMGUR_CLIENT_ID");
if (clientId == null || clientId.isBlank()) {
throw new IllegalStateException("IMGUR_CLIENT_ID env var not set.");
}
File file = new File("docs/images/logo-square.png");
if (!file.isFile() || file.length() == 0) {
throw new IllegalArgumentException("Missing or empty upload file: " + file);
}
RequestBody requestBody = RequestBody.create(file, MEDIA_TYPE_PNG);
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + clientId)
.url("https://api.imgur.com/3/image")
.post(new ProgressRequestBody(requestBody, progressListener))
.build();
// Post-call: classify Imgur-specific status.
try (Response response = client.newCall(request).execute()) {
String body = response.body() != null ? response.body().string() : "";
if (response.code() == 401 || response.code() == 403) {
throw new AuthException("Imgur rejected Client-ID (" + response.code() + "): " + body);
}
if (response.code() == 429) {
throw new RateLimitedException(response.header("X-RateLimit-UserReset"));
}
if (!response.isSuccessful()) throw new IOException("Imgur " + response.code() + ": " + body);
} Type guard
private static boolean imgurAuthFailed(Response r) {
return r.code() == 401 || r.code() == 403;
}
private static boolean imgurRateLimited(Response r) {
return r.code() == 429
|| "0".equals(r.header("X-RateLimit-UserRemaining"));
} Try / catch
try (Response response = client.newCall(request).execute()) {
String body = response.body() != null ? response.body().string() : "";
if (imgurAuthFailed(response)) {
throw new AuthException("Imgur auth failed (" + response.code() + "): " + body);
}
if (imgurRateLimited(response)) {
throw new RateLimitedException(response.header("X-RateLimit-UserReset"));
}
if (!response.isSuccessful()) {
throw new IOException("Imgur upload failed: HTTP " + response.code() + ": " + body);
}
System.out.println(body);
} catch (RateLimitedException e) {
sleepUntil(e.resetEpoch()); // then optionally retry
} catch (AuthException e) {
// surface to operator — credentials must be rotated manually
throw e;
} Prevention
- Never ship shared sample credentials like `IMGUR_CLIENT_ID = "9199fdef135c122"`; register your own app.
- Verify the upload file exists and is non-empty before constructing the RequestBody (avoids spurious 400s).
- Always read the response body even on error — Imgur returns a JSON error message you need.
- Track Imgur rate-limit headers (`X-RateLimit-ClientRemaining`, `X-RateLimit-UserRemaining`) and back off proactively.
When it happens
Trigger: POST to `https://api.imgur.com/3/image` with `Authorization: Client-ID 9199fdef135c122` (a sample/revoked credential) returns 401/403 because Imgur now requires per-application registration and has retired widely-shared demo client IDs. Also fires on 429 (Imgur rate limits, ~12,500/day per app, 50/min anonymous), 400 (missing/malformed multipart body or unsupported media), or 404 if the endpoint path changes.
Common situations: Running the recipe as-is with the leaked `IMGUR_CLIENT_ID`; registering an Imgur app but forgetting to pass the new client ID; file `docs/images/logo-square.png` missing or unreadable so the body is empty (server returns 400); Imgur migrating endpoint versions; Imgur blocking uploads from cloud/datacenter IPs.
Related errors
AI-assisted analysis of square/okhttp@91a8b34c6f (2026-08-10).
Data as JSON: /api/errors/4368d499451ca672.
Report an issue: GitHub.
Appendix: source
Thrown at samples/guide/src/main/java/okhttp3/recipes/UploadProgress.java:75
if (contentLength != -1) {
System.out.format("%d%% done\n", (100 * bytesWritten) / contentLength);
}
}
}
};
RequestBody requestBody = RequestBody.create(
new File("docs/images/logo-square.png"),
MEDIA_TYPE_PNG);
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(new ProgressRequestBody(requestBody, progressListener))
.build();
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 UploadProgress().run();
}
private static class ProgressRequestBody extends RequestBody {
private final ProgressListener progressListener;
private final RequestBody delegate;
public ProgressRequestBody(RequestBody delegate, ProgressListener progressListener) {
this.delegate = delegate;
this.progressListener = progressListener;
}
View on GitHub (pinned to 91a8b34c6f)