{"id":"cb977d990cba03b3","repo":"square/okhttp","slug":"unexpected-code","errorCode":null,"errorMessage":"Unexpected code ","messagePattern":"Unexpected code ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"samples/guide/src/main/java/okhttp3/recipes/AccessHeaders.java","lineNumber":35,"sourceCode":"\nimport java.io.IOException;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\npublic final class AccessHeaders {\n  private final OkHttpClient client = new OkHttpClient();\n\n  public void run() throws Exception {\n    Request request = new Request.Builder()\n        .url(\"https://api.github.com/repos/lysine-dev/okhttp/issues\")\n        .header(\"User-Agent\", \"OkHttp Headers.java\")\n        .addHeader(\"Accept\", \"application/json; q=0.5\")\n        .addHeader(\"Accept\", \"application/vnd.github.v3+json\")\n        .build();\n\n    try (Response response = client.newCall(request).execute()) {\n      if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\n\n      System.out.println(\"Server: \" + response.header(\"Server\"));\n      System.out.println(\"Date: \" + response.header(\"Date\"));\n      System.out.println(\"Vary: \" + response.headers(\"Vary\"));\n    }\n  }\n\n  public static void main(String... args) throws Exception {\n    new AccessHeaders().run();\n  }\n}\n","sourceCodeStart":17,"sourceCodeEnd":47,"githubUrl":"https://github.com/square/okhttp/blob/4fc083138014aba3d0078f5c26d1ce84815fa984/samples/guide/src/main/java/okhttp3/recipes/AccessHeaders.java#L17-L47","documentation":"This is a sample-recipe guard (not built into OkHttp): after client.newCall(request).execute() returns a Response, the code checks response.isSuccessful() (true only for HTTP status 200-299) and throws java.io.IOException(\"Unexpected code \" + response) for anything else. The Response.toString() appended to the message contains the full status line, URL, and headers, which is the only diagnostic you get. OkHttp itself never throws this — it is the recipe author's convention for turning a transport-success-but-HTTP-error into an exception.","triggerScenarios":"GET https://api.github.com/repos/lysine-dev/okhttp/issues with custom Accept + User-Agent headers. Surfaces when GitHub returns 404 (repo renamed/deleted/non-existent), 301/302 the recipe does not follow (it will follow by default), 403 (unauthenticated rate limit exceeded — 60 req/hr per IP), or 410. response.code() will be the actual failing status.","commonSituations":"Hitting GitHub's unauthenticated rate limit (403 with X-RateLimit-Remaining: 0); copying the recipe but forgetting the User-Agent header (GitHub rejects requests without one); targeting a repo slug that does not exist or was renamed; running behind a shared NAT IP where the 60/hr limit is consumed by others.","solutions":["Inspect response.code() and response.headers() (X-RateLimit-Remaining, X-RateLimit-Reset) before throwing to see the real cause.","If 403 rate-limited, authenticate the request with a GitHub token (Authorization: Bearer <token>) to raise the limit, or add caching/backoff.","If 404, verify the repo owner/name in the URL is correct (the recipe pins lysine-dev/okhttp).","Keep the User-Agent header (recipe sets 'OkHttp Headers.java'); GitHub requires a non-empty UA.","Branch on response.isSuccessful() instead of throwing, so 3xx/4xx/5xx can be handled individually."],"exampleFix":"// before\ntry (Response response = client.newCall(request).execute()) {\n  if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\n  ...\n}\n\n// after\ntry (Response response = client.newCall(request).execute()) {\n  if (response.code() == 403) {\n    throw new IOException(\"GitHub rate-limited; remaining=\"\n        + response.header(\"X-RateLimit-Remaining\"));\n  }\n  if (!response.isSuccessful()) {\n    throw new IOException(\"GitHub \" + response.code() + \" for \" + response.request().url());\n  }\n  ...\n}","handlingStrategy":"validation","validationCode":"// Validate the GitHub target before relying on the response\nRequest request = new Request.Builder()\n    .url(\"https://api.github.com/repos/lysine-dev/okhttp/issues\")\n    .header(\"User-Agent\", \"MyApp/1.0 (contact@example.com)\")  // GitHub requires this\n    .header(\"Authorization\", \"Bearer \" + token)                // lifts 60/hr cap\n    .header(\"Accept\", \"application/vnd.github+json\")\n    .build();\ntry (Response response = client.newCall(request).execute()) {\n  if (response.code() == 403 || response.code() == 429) {\n    // respect X-RateLimit-Reset before retrying\n  }\n  if (!response.isSuccessful()) { /* handle by code */ }\n}","typeGuard":null,"tryCatchPattern":"try (Response response = client.newCall(request).execute()) {\n  if (!response.isSuccessful()) {\n    switch (response.code()) {\n      case 403: throw new RateLimitedException(response.header(\"X-RateLimit-Reset\"));\n      case 404: throw new NotFoundException(response.request().url().toString());\n      default:  throw new HttpException(response.code(), response.message());\n    }\n  }\n  // use response\n}","preventionTips":["Always send a User-Agent header to GitHub.","Authenticate requests to lift the 60/hr unauthenticated cap.","Branch on response.code() instead of a single isSuccessful() throw.","Check X-RateLimit-Remaining and back off before the limit resets."],"tags":["okhttp","http-status","github-api","rate-limit","java"],"analyzedSha":"4fc083138014aba3d0078f5c26d1ce84815fa984","analyzedAt":"2026-08-04T19:09:04.639Z","schemaVersion":2}