skip to content

What is the ResourceResolver / ResourceChain, and what are the built-in resolvers in Spring MVC?

level: seniorimportance: should knowfreq 40%

answer

  1. chain of ResourceResolver, terminal = PathResourceResolver
  2. resourceChain(true) => adds CachingResourceResolver
  3. VersionResourceResolver: content hash vs fixed strategy
  4. EncodedResourceResolver serves .gz/.br
  5. CssLinkResourceTransformer rewrites url()/@import

basics

~10 s

A 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 s

When 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
java
@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

for a junior

Likely unaware; may just know resources are 'found somewhere'.

for a middle

Knows resourceChain(true) enables caching and versioning can be added.

for a senior

Can name the resolvers, explain ordering and the terminal PathResourceResolver, and the transformer pipeline.

for a principal

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

context