square/okhttp · error · IOException
Unexpected code
Error message
Unexpected code
What it means
Recipe-level guard after a GET configured with a preemptive Basic auth interceptor (BasicAuthInterceptor adds Authorization: Basic <creds> for host publicobject.com before the request goes out). Because auth is sent on the first request (not after a 401 challenge), a wrong credential yields a final 401 that reaches this line, where java.io.IOException("Unexpected code " + response) is thrown.
Source
Thrown at samples/guide/src/main/java/okhttp3/recipes/PreemptiveAuth.java:41
import okhttp3.Response;
public final class PreemptiveAuth {
private final OkHttpClient client;
public PreemptiveAuth() {
client = new OkHttpClient.Builder()
.addInterceptor(
new BasicAuthInterceptor("publicobject.com", "jesse", "password1"))
.build();
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://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 PreemptiveAuth().run();
}
static final class BasicAuthInterceptor implements Interceptor {
private final String credentials;
private final String host;
BasicAuthInterceptor(String host, String username, String password) {
this.credentials = Credentials.basic(username, password);
this.host = host;
}
View on GitHub (pinned to 4fc0831380)
Solutions
- Verify the credentials are valid for the realm.
- Make the host comparison robust: request.url().host().equalsIgnoreCase(host).
- Use https:// so the preemptive header is encrypted.
- Inspect response.code() to distinguish 401 (creds) from 403 (forbidden) from 404 (missing).
Example fix
// before
if (request.url().host().equals(host)) {
request = request.newBuilder().header("Authorization", credentials).build();
}
...
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
// after
if (request.url().host().equalsIgnoreCase(host)) {
request = request.newBuilder().header("Authorization", credentials).build();
}
...
if (response.code() == 401) {
throw new IOException("Preemptive auth rejected for " + request.url());
}
if (!response.isSuccessful()) {
throw new IOException("HTTP " + response.code() + " for " + request.url());
} Defensive patterns
Strategy: validation
Validate before calling
// Make host matching robust and verify credentials before relying on the request
// inside the interceptor:
if (request.url().host().equalsIgnoreCase(host)) {
request = request.newBuilder().header("Authorization", credentials).build();
}
// before calling, sanity-check:
if (username == null || password == null) throw new IllegalArgumentException("creds required");
// prefer https so the header is encrypted Try / catch
try (Response response = client.newCall(request).execute()) {
if (response.code() == 401) throw new AuthException("Preemptive Basic rejected for " + request.url());
if (response.code() == 403) throw new ForbiddenException(request.url().toString());
if (!response.isSuccessful()) throw new HttpException(response.code(), response.message());
} Prevention
- Compare hosts case-insensitively (equalsIgnoreCase).
- Use https:// when sending preemptive Basic credentials.
- Confirm credentials against the server's realm before deploying.
- Restrict the interceptor to the exact host to avoid leaking credentials to other domains.
When it happens
Trigger: GET https://publicobject.com/secrets/hellosecret.txt with an always-on Authorization header. Fires on 401 (wrong username/password), 403 (authenticated but forbidden), or 404 (resource gone). Because the interceptor only injects the header when request.url().host() equals 'publicobject.com', a host mismatch silently sends no auth and you can still get 401.
Common situations: Placeholder credentials 'jesse/password1' do not match the target; host check is case-sensitive — a URL with uppercase host or a trailing-dot host (publicobject.com.) will not match and auth is omitted; sending Basic over http leaks credentials.
Related errors
AI-assisted analysis of square/okhttp@4fc0831380 (2026-08-04).
Data as JSON: /data/errors/a69f9973cf4d3782.json.
Report an issue: GitHub.