apache/iceberg · error · RESTException
Invalid (NOT_MODIFIED) response for request: method=
Error message
Invalid (NOT_MODIFIED) response for request: method=%s, path=%s
What it means
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.
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.
Example fix
// before: server returns 304 regardless of request headers
// after: server-side guard
if (request.getHeader("If-None-Match") != null && etagMatches(request, currentETag)) {
return Response.notModified().tag(currentETag).build();
}
return Response.ok(payload).build(); Defensive patterns
Strategy: try-catch
Validate before calling
// before calling: verify conditional headers are set when freshness-aware
Preconditions.checkArgument(
!expecting304 || request.headers().contains("If-None-Match"),
"304-capable requests must send If-None-Match"); Try / catch
try {
return client.head(...);
} catch (RESTException e) {
if (e.getMessage().contains("NOT_MODIFIED")) {
// server/proxy sends 304 without negotiation — bypass cache or treat as cache bug
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid (null) response body for request
- An error occurred reloading table
- An error occurred reloading table
- Cannot assume role to sign REST requests because is not…
- Cannot build StorageCredential, some of required attributes…
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/234f57e4371e671b.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/apache/iceberg/rest/HTTPClient.java:354
Consumer<ErrorResponse> errorHandler,
Consumer<Map<String, String>> responseHeaders,
ParserContext parserContext)
throws IOException {
Map<String, String> respHeaders = Maps.newHashMap();
for (Header header : response.getHeaders()) {
respHeaders.put(header.getName(), header.getValue());
}
responseHeaders.accept(respHeaders);
// Skip parsing the response stream for any successful request not expecting a response body
if (emptyBody(response, responseType)) {
if (response.getCode() == HttpStatus.SC_NOT_MODIFIED
&& !request.headers().contains(HttpHeaders.IF_NONE_MATCH)) {
// 304-NOT_MODIFIED is used for freshness-aware loading and requires an ETag sent to the
// server via IF_NONE_MATCH header in the request. If no ETag was sent, we shouldn't
// receive a 304.
throw new RESTException(
"Invalid (NOT_MODIFIED) response for request: method=%s, path=%s",
request.method(), request.path());
}
return null;
}
if (!isSuccessful(response)) {
// The provided error handler is expected to throw, but a RESTException is thrown if not.
String responseBody = extractResponseBodyAsString(response);
throwFailure(response, responseBody, errorHandler);
}
if (response.getEntity() == null) {
throw new RESTException(
"Invalid (null) response body for request (expected %s): method=%s, path=%s, status=%d",
responseType.getSimpleName(), request.method(), request.path(), response.getCode());
}View on GitHub (pinned to 86d9c8fc54)