square/okhttp · error · IOException
Unexpected code
Error message
Unexpected code
What it means
Thrown after the configured Authenticator runs: `if (!response.isSuccessful()) throw new IOException("Unexpected code " + response)`. The authenticator adds Basic credentials when the server issues a 401 challenge. If auth succeeds the final response is 2xx; if it fails (wrong credentials, the server still returns 401/403), response.isSuccessful() is false and this throws. The authenticator itself bails out (returns null) if it already tried once.
Solutions
- Replace "jesse"/"password1" with valid credentials for the resource you are actually hitting.
- Log response.code() and response.challenges() (as the sample already does) to see the auth scheme and status.
- Confirm the protected path still exists; 404 is not an auth failure.
- If the server uses a non-Basic scheme, implement an Authenticator that handles that scheme or returns null to stop.
Example fix
// before
String credential = Credentials.basic("jesse", "password1");
...
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
// after
String credential = Credentials.basic(realUser, realPassword);
...
if (response.code() == 401 || response.code() == 403) {
throw new IOException("Authentication failed (" + response.code() + ")");
}
if (!response.isSuccessful()) throw new IOException("HTTP " + response.code()); Defensive patterns
Strategy: try-catch
Validate before calling
// Inspect challenges + code before trusting the response.
if (response.code() == 401) {
List<Challenge> challenges = response.challenges();
// If Basic is not among them, the authenticator cannot help.
} Type guard
static boolean authenticated(Response r) { return r.code() != 401 && r.code() != 403; } Try / catch
try {
// call
} catch (IOException e) {
if (e.getMessage().contains("Unexpected code")) {
// auth or status failure from the recipe guard; re-auth or surface to user
}
} Prevention
- Do not hardcode sample credentials; load real credentials from config/secrets.
- Check response.challenges() to confirm the server uses Basic auth.
- Return null from your Authenticator to stop infinite retry loops.
- Distinguish 401 (auth) from 404 (missing resource).
When it happens
Trigger: Credentials.basic("jesse", "password1") do not match the protected resource, so publicobject.com reponds 401 a second time; the resource path /secrets/hellosecret.txt was removed and returns 404; the server uses a non-Basic scheme the authenticator does not satisfy; the host no longer requires auth and returns a redirect.
Common situations: Using the literal sample credentials against a live server; the secret file was deleted; the server switched to digest/OAuth; copy-pasting the sample without changing username/password to real values.
Related errors
AI-assisted analysis of square/okhttp@91a8b34c6f (2026-08-10).
Data as JSON: /api/errors/86751ab560a219f0.
Report an issue: GitHub.
Appendix: source
Thrown at samples/guide/src/main/java/okhttp3/recipes/Authenticate.java:50
}
System.out.println("Authenticating for response: " + response);
System.out.println("Challenges: " + response.challenges());
String credential = Credentials.basic("jesse", "password1");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
})
.build();
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.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 Authenticate().run();
}
}
View on GitHub (pinned to 91a8b34c6f)