{"record":{"id":"234f57e4371e671b","repo":"apache/iceberg","slug":"invalid-not-modified-response-for-request-metho","errorCode":null,"errorMessage":"Invalid (NOT_MODIFIED) response for request: method=%s, path=%s","messagePattern":"Invalid \\(NOT_MODIFIED\\) response for request: method=(.+?), path=(.+?)","errorType":"exception","errorClass":"RESTException","httpStatus":304,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/rest/HTTPClient.java","lineNumber":354,"sourceCode":"      Consumer<ErrorResponse> errorHandler,\n      Consumer<Map<String, String>> responseHeaders,\n      ParserContext parserContext)\n      throws IOException {\n    Map<String, String> respHeaders = Maps.newHashMap();\n    for (Header header : response.getHeaders()) {\n      respHeaders.put(header.getName(), header.getValue());\n    }\n\n    responseHeaders.accept(respHeaders);\n\n    // Skip parsing the response stream for any successful request not expecting a response body\n    if (emptyBody(response, responseType)) {\n      if (response.getCode() == HttpStatus.SC_NOT_MODIFIED\n          && !request.headers().contains(HttpHeaders.IF_NONE_MATCH)) {\n        // 304-NOT_MODIFIED is used for freshness-aware loading and requires an ETag sent to the\n        // server via IF_NONE_MATCH header in the request. If no ETag was sent, we shouldn't\n        // receive a 304.\n        throw new RESTException(\n            \"Invalid (NOT_MODIFIED) response for request: method=%s, path=%s\",\n            request.method(), request.path());\n      }\n\n      return null;\n    }\n\n    if (!isSuccessful(response)) {\n      // The provided error handler is expected to throw, but a RESTException is thrown if not.\n      String responseBody = extractResponseBodyAsString(response);\n      throwFailure(response, responseBody, errorHandler);\n    }\n\n    if (response.getEntity() == null) {\n      throw new RESTException(\n          \"Invalid (null) response body for request (expected %s): method=%s, path=%s, status=%d\",\n          responseType.getSimpleName(), request.method(), request.path(), response.getCode());\n    }","sourceCodeStart":336,"sourceCodeEnd":372,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/rest/HTTPClient.java#L336-L372","documentation":"handleResponse rejects an HTTP 304 (NOT_MODIFIED) response when the original request did not send an If-None-Match header. 304 is only meaningful for freshness-aware (ETag/If-None-Match based) loading; receiving one without having sent an ETag means the server is misbehaving or the response is misrouted. The client throws RESTException instead of returning an empty body.","triggerScenarios":"A request whose responseType permits an empty body (e.g. loadTable with HEAD-style freshness checks) receives status 304 while request.headers() lacks HttpHeaders.IF_NONE_MATCH — i.e. the server sends 304 unconditionally or a proxy/cache injects it.","commonSituations":"A caching proxy or CDN returning stale 304s; a custom/buggy REST catalog server that always returns 304 for conditional-style endpoints; server upgraded with freshness-aware support but returning 304 even when no ETag was negotiated.","solutions":["Fix or bypass the caching layer (proxy/CDN) that is returning 304 without honoring If-None-Match.","Report/patch the catalog server: it must only return 304 when the request carried If-None-Match.","If the client intended freshness-aware loading, ensure the ETag from the previous response is sent via the If-None-Match request header.","Bypass the proxy for the catalog endpoint to confirm the error disappears without it."],"exampleFix":"// before: server returns 304 regardless of request headers\n// after: server-side guard\nif (request.getHeader(\"If-None-Match\") != null && etagMatches(request, currentETag)) {\n  return Response.notModified().tag(currentETag).build();\n}\nreturn Response.ok(payload).build();","handlingStrategy":"try-catch","validationCode":"// before calling: verify conditional headers are set when freshness-aware\nPreconditions.checkArgument(\n    !expecting304 || request.headers().contains(\"If-None-Match\"),\n    \"304-capable requests must send If-None-Match\");","typeGuard":null,"tryCatchPattern":"try {\n  return client.head(...);\n} catch (RESTException e) {\n  if (e.getMessage().contains(\"NOT_MODIFIED\")) {\n    // server/proxy sends 304 without negotiation — bypass cache or treat as cache bug\n  }\n  throw e;\n}","preventionTips":["Only send 304 from servers that received If-None-Match.","Exclude catalog endpoints from aggressive CDN/proxy caching.","Always propagate the ETag from prior responses into If-None-Match."],"tags":["http","etag","caching","rest-catalog"],"backgroundTag":"unexpected-http-status","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}