What is the ResourceResolver / ResourceChain, and what are the built-in resolvers in Spring MVC?
answer
- chain of ResourceResolver, terminal = PathResourceResolver
- resourceChain(true) => adds CachingResourceResolver
- VersionResourceResolver: content hash vs fixed strategy
- EncodedResourceResolver serves .gz/.br
- CssLinkResourceTransformer rewrites url()/@import
basics
~10 sA ResourceChain is an ordered pipeline of ResourceResolver objects that turn a request path into an actual Resource. Built-ins include PathResourceResolver, CachingResourceResolver, VersionResourceResolver, EncodedResourceResolver, and WebJarsResourceResolver.
solid answer
~40 sWhen ResourceHttpRequestHandler receives a request, it delegates path-to-Resource resolution to a chain of ResourceResolvers, each optionally rewriting the path or short-circuiting. You enable it with resourceChain(true) on the registration. Key resolvers: PathResourceResolver (terminal — finds the file under the configured locations, blocking path traversal); CachingResourceResolver (caches resolved lookups in a Cache to avoid repeated disk/hash work); VersionResourceResolver (strips/validates a content hash or fixed version from the URL); EncodedResourceResolver (serves precompressed .gz/.br variants when the client accepts them); WebJarsResourceResolver (resolves version-agnostic /webjars paths). A parallel ResourceTransformer chain (e.g. CssLinkResourceTransformer) rewrites links inside served files. resourceChain(true) also implies a CachingResourceResolver by default. Order matters: version and caching resolvers run before the terminal PathResourceResolver.
code
java · 9 lines@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).immutable())
.resourceChain(true)
.addResolver(new VersionResourceResolver()
.addContentVersionStrategy("/**")); // app-<md5>.js
}go deeper
Likely unaware; may just know resources are 'found somewhere'.
Knows resourceChain(true) enables caching and versioning can be added.
Can name the resolvers, explain ordering and the terminal PathResourceResolver, and the transformer pipeline.
Reasons about URL generation direction, ResourceUrlProvider integration, custom resolvers, and CDN/compression strategy.
## The core abstraction `ResourceHttpRequestHandler` doesn't just open a file at the requested path — it runs the request path through a **ResourceResolverChain**, an ordered list of `ResourceResolver` beans. Each resolver's `resolveResource(request, requestPath, locations, chain)` either resolves the resource itself, rewrites the path and delegates to `chain.resolveResource(...)`, or returns null. There is a symmetric `resolveUrlPath` direction used to generate the public URL for a given internal resource (used by `ResourceUrlProvider` / `ResourceUrlEncodingFilter`). ## Enabling the chain ```java registry.addResourceHandler("/assets/**") .addResourceLocations("classpath:/static/") .resourceChain(true); // true = cache resolved resources ``` `resourceChain(cacheResources)` sets up the chain; passing `true` adds a `CachingResourceResolver`/`CachingResourceTransformer` backed by a `ConcurrentMapCache` (or a `Cache` you supply). You can also call `.addResolver(...)`/`.addTransformer(...)` to customize. ## Built-in ResourceResolvers - **PathResourceResolver** — the **terminal** resolver. Locates the file under the configured `Resource` locations and enforces that the resolved path stays inside them (security: blocks `../` traversal). Always last in the chain. - **CachingResourceResolver** — wraps lookups in a `Cache` so repeated requests skip disk access and hash computation. Should be first so cache hits short-circuit the whole chain. - **VersionResourceResolver** — handles **content versioning**. Configured with a `VersionStrategy` per path pattern: `addContentVersionStrategy("/**")` (MD5 content hash embedded in the filename, e.g. `app-9f8a2c1e.js`) or `addFixedVersionStrategy("v3", "/**")` (a fixed token, e.g. a build number as a path prefix). It strips the version from the incoming path before delegating, and validates the version matches the actual content — a mismatch yields 404. - **EncodedResourceResolver** — serves precompressed variants (`.gz`, `.br`) when the request's `Accept-Encoding` allows, setting `Content-Encoding` accordingly. - **WebJarsResourceResolver** — resolves version-agnostic WebJars URLs like `/webjars/jquery/jquery.js` to the versioned classpath location (requires `webjars-locator-core`). ## ResourceTransformers (the parallel pipeline) After a resource is resolved, a chain of `ResourceTransformer`s may rewrite its content: - **CssLinkResourceTransformer** — rewrites `url(...)` and `@import` links inside CSS to their versioned equivalents, so fingerprinting cascades through CSS. - **CachingResourceTransformer** — caches transformed output. ## Generating versioned URLs Resolving an incoming versioned request is only half the job — templates must **emit** versioned URLs. `ResourceUrlProvider` (and in views, the `ResourceUrlEncodingFilter` + Thymeleaf/JSP integration) maps a logical path to its current versioned URL by running the chain in the `resolveUrlPath` direction. ## Gotchas & ordering - Order is significant: Caching first (short-circuit hits), then Version/Encoded, then the terminal Path resolver. - `VersionResourceResolver` with `ContentVersionStrategy` computes an MD5 of file content — pair it with long-lived immutable Cache-Control for real benefit. - Fixed vs content strategy: content strategy busts cache automatically per-file on change; fixed strategy busts everything on each build number bump. - The chain is per-registration; different URL patterns can have different chains.
- Why must PathResourceResolver be last in the chain?It is the terminal resolver that actually opens the file from the configured locations and enforces the security boundary. Earlier resolvers rewrite/validate the path (strip version, pick encoding) and then delegate; PathResourceResolver resolves the final, cleaned path to real bytes.
- How does a Thymeleaf template know to output app-9f8a2c.js instead of app.js?Through ResourceUrlProvider / ResourceUrlEncodingFilter, which run the resolver chain in the resolveUrlPath direction to translate a logical path to its current versioned URL, integrated with the view technology's URL handling (e.g. th:href with @{...}).
saying these in an interview costs you the question
- Thinking resolvers only go path->resource and ignoring the resolveUrlPath (URL generation) direction
- Believing VersionResourceResolver rewrites HTML automatically without ResourceUrlProvider/transformers
- Putting CachingResourceResolver last instead of first
- Assuming resourceChain(true) does nothing beyond caching — it also enables the resolver/transformer pipeline