What does ShallowEtagHeaderFilter do, how does it generate an ETag, and what is it NOT good for?
answer
- buffer body -> MD5 hash -> ETag
- If-None-Match match -> 304 empty body
- 'shallow' = saves bandwidth, not CPU/DB
- buffers whole response -> bad for streaming/large
- setWriteWeakETag(true) for W/"..."
basics
~20 sIt's a servlet filter that buffers the full response, computes an MD5 hash of the body, and sends it as the ETag header. On the next request Spring compares the client's If-None-Match; if it matches, it returns 304 Not Modified with no body — saving bandwidth, not server work.
solid answer
~40 sorg.springframework.web.filter.ShallowEtagHeaderFilter wraps the response in a buffer, lets the handler render fully, then computes an MD5 hash over the captured bytes and writes it as a strong ETag (e.g. "0abc..."). If the incoming If-None-Match equals that ETag, the filter drops the body and returns 304 Not Modified. It's 'shallow' precisely because the whole response was still generated and hashed — you save network bandwidth but not CPU/DB work. Register it as a bean or via FilterRegistrationBean. Limits: it buffers the entire response (bad for large/streaming responses), can't produce weak ETags unless writeWeakETag=true, is skipped when the response already carries caching headers or isn't cacheable, and offers no shortcut to avoid computing the payload. For that, use handler-level validation (WebRequest.checkNotModified) instead.
code
java · 20 linesimport org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.*;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
@Configuration
class CachingConfig {
@Bean
FilterRegistrationBean<ShallowEtagHeaderFilter> etagFilter() {
ShallowEtagHeaderFilter filter = new ShallowEtagHeaderFilter();
// filter.setWriteWeakETag(true); // emit W/"..." instead of a strong tag
FilterRegistrationBean<ShallowEtagHeaderFilter> reg =
new FilterRegistrationBean<>(filter);
reg.addUrlPatterns("/api/*");
reg.setName("etagFilter");
return reg;
}
}
// Flow: 1st request -> 200 OK, ETag: "0abc123..."
// 2nd request with If-None-Match: "0abc123..." -> 304 Not Modified, empty bodygo deeper
Know it's a filter that adds an ETag and can return 304 automatically.
Explain the buffer-then-MD5 mechanism, the strong-ETag format, and that it saves bandwidth not compute.
Weigh it against handler-level checkNotModified, register it selectively, and avoid it for streaming/large bodies; know the eligibility skip rules.
Decide filter vs handler validation per endpoint based on cost profile, and reason about memory buffering, weak vs strong tags, and proxy/CDN interaction across the API surface.
## The problem it solves An **ETag** (entity tag) is an opaque identifier for a specific version of a resource. The client stores it and sends it back in the `If-None-Match` request header. If the server's current ETag matches, the resource is unchanged and the server replies `304 Not Modified` **with no body**, saving the download. ## What ShallowEtagHeaderFilter does `org.springframework.web.filter.ShallowEtagHeaderFilter` is a servlet `Filter` that automates ETags for you: 1. It wraps the `HttpServletResponse` in a buffering wrapper (`ContentCachingResponseWrapper`) so the body is captured in memory instead of streamed to the client. 2. It lets the downstream handler render the response completely. 3. It computes an **MD5 hash** over the buffered bytes and writes it as a **strong ETag**, formatted like `"0<hex-md5>"` (the leading `0` is part of Spring's format; a weak tag is prefixed `W/`). 4. It reads the request's `If-None-Match`. If it equals the computed ETag, it **discards the body and returns `304 Not Modified`**; otherwise it flushes the buffered body with the ETag header and `200 OK`. ## Why 'shallow' Because the handler **already did all the work** — the controller ran, the DB was queried, the response was fully rendered and hashed. The only thing saved is **network bandwidth** (the body isn't transmitted on a 304). Server CPU, database load, and rendering time are **not** saved. That's the defining limitation. ## Registration ```java @Bean FilterRegistrationBean<ShallowEtagHeaderFilter> etagFilter() { var reg = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); reg.addUrlPatterns("/api/*"); reg.setName("etagFilter"); return reg; } ``` Simply declaring `@Bean ShallowEtagHeaderFilter` also works (auto-registered across all requests). ## Weak ETags By default it emits a **strong** ETag (byte-for-byte identity). Call `setWriteWeakETag(true)` to emit `W/"..."` — a weak tag signals semantic (not byte-exact) equivalence, which is more forgiving of things like compression differences. ## When the filter is skipped Spring's `isEligibleForEtag(...)` skips ETag generation when: the response status isn't a 2xx success (or 3xx as configured), the response already contains a `Cache-Control` header with `no-store`, or the request/response indicates the content shouldn't be cached. So an ETag won't always appear. ## Gotchas - **Buffers the entire response in memory** → unsuitable for large downloads or streaming (`StreamingResponseBody`, SSE). Memory pressure and broken streaming result. - **No compute savings** → if your goal is avoiding expensive DB/rendering work, the filter can't help; you must check the validator *before* doing the work with `WebRequest.checkNotModified`. - **MD5 cost** → hashing large bodies is itself work. - **Only handles If-None-Match**, i.e. ETag validation; it doesn't do Last-Modified/If-Modified-Since logic. ## When to use Use `ShallowEtagHeaderFilter` for small/medium dynamic responses where recomputation is cheap but you want to cut redundant downloads with zero controller changes. For expensive resources, prefer handler-level `checkNotModified` so you can short-circuit before rendering.
- Does ShallowEtagHeaderFilter reduce database or CPU load?No. The handler fully executes and renders the response before the filter hashes it; only the network transfer of the body is avoided on a 304. To skip the expensive work itself, validate before doing it using WebRequest.checkNotModified.
- Why is it a poor fit for streaming or very large responses?It buffers the entire response in memory (ContentCachingResponseWrapper) to hash it, which breaks streaming and can cause memory pressure for large payloads.
saying these in an interview costs you the question
- Claiming the filter avoids running the controller / DB query
- Thinking it computes the ETag before rendering the body
- Assuming it produces weak ETags by default
- Enabling it for large downloads or SSE/streaming endpoints