skip to content

MVC Configuration & Infrastructure

Configuring the MVC infrastructure itself: HTTP caching, static resources, safe URI construction, and the WebMvcConfigurer callbacks. Small topics individually, but they are where Boot's defaults stop and your choices start.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

20

How do you add a Cache-Control response header to a Spring MVC controller method, and what does the CacheControl builder give you?

level: juniorimportance: must knowfreq 62%

answer

  1. org.springframework.http.CacheControl builder
  2. ResponseEntity.ok().cacheControl(...)
  3. maxAge / noStore / noCache / cachePublic-Private
  4. header policy, NOT server-side caching
  5. also static resources + WebContentInterceptor

basics

~10 s

Return a ResponseEntity and call .cacheControl(...) with Spring's CacheControl builder, e.g. CacheControl.maxAge(Duration.ofMinutes(10)).cachePublic(). Spring writes the correct Cache-Control header (max-age=600, public) so browsers/proxies can cache the response.

solid answer

~30 s

Spring's org.springframework.http.CacheControl is a fluent builder that produces a valid Cache-Control header string, so you don't hand-write directives. The common path is ResponseEntity.ok().cacheControl(CacheControl.maxAge(Duration.ofMinutes(10)).cachePublic()).body(dto). maxAge sets max-age (freshness lifetime); cachePublic/cachePrivate control shared vs per-user caches; noStore forbids storing at all; noCache forces revalidation before reuse; mustRevalidate, sMaxAge (shared caches), staleWhileRevalidate and staleIfError map to the matching directives. Beyond controllers you can attach it to static resources (ResourceHandlerRegistration.setCacheControl) or globally via a WebContentInterceptor. The builder is just header construction — it does no server-side caching itself; it only tells clients and intermediaries how they may cache the response.

code

java · 29 lines
java
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.Duration;

@RestController
@RequestMapping("/api/products")
class ProductController {

    private final ProductService service;
    ProductController(ProductService service) { this.service = service; }

    @GetMapping("/{id}")
    ResponseEntity<ProductDto> get(@PathVariable long id) {
        ProductDto dto = service.find(id);
        CacheControl cc = CacheControl.maxAge(Duration.ofMinutes(10))
                                      .cachePublic();
        // Cache-Control: max-age=600, public
        return ResponseEntity.ok().cacheControl(cc).body(dto);
    }

    @GetMapping("/me")
    ResponseEntity<ProfileDto> myProfile() {
        // user-specific: never store in shared caches, always revalidate
        return ResponseEntity.ok()
                .cacheControl(CacheControl.noCache().cachePrivate())
                .body(service.currentUser());
    }
}

go deeper

for a junior

Know it's a fluent builder set via ResponseEntity.cacheControl and that maxAge/public are the common knobs.

for a middle

Distinguish no-cache vs no-store, public vs private, and know it writes headers only.

for a senior

Combine max-age freshness with ETag validation; apply CacheControl to static resources and interceptors; use private/sMaxAge deliberately for CDNs.

for a principal

Reason about CDN/shared-cache correctness (private vs s-maxage), stale-while-revalidate for resilience, and separating transport caching from application caching in the architecture.

## What Cache-Control is `Cache-Control` is an HTTP response header that tells browsers and intermediary caches (CDNs, proxies) whether and how long they may reuse a response instead of re-requesting it. Example: `Cache-Control: max-age=600, public` means "any cache may store this and serve it without asking the server again for 600 seconds." ## Spring's CacheControl builder Hand-writing the header string is error-prone, so Spring provides `org.springframework.http.CacheControl` — a **fluent builder** whose terminal object serializes to a correct header value. You never call a `build()` — you pass the `CacheControl` instance to Spring, which serializes it. Key factory + builder methods: - `CacheControl.maxAge(Duration)` / `maxAge(long, TimeUnit)` → `max-age=<seconds>`. The freshness lifetime. - `CacheControl.noStore()` → `no-store`. Nothing may be cached anywhere (use for sensitive data). - `CacheControl.noCache()` → `no-cache`. May be stored but must revalidate with the origin before every reuse. - `CacheControl.empty()` → no directives; a base to add things like `mustRevalidate()`. - On the returned builder: `.cachePublic()` → `public`; `.cachePrivate()` → `private` (only the end-user's browser, not shared caches); `.mustRevalidate()`; `.proxyRevalidate()`; `.sMaxAge(Duration)` → `s-maxage` (overrides max-age for shared caches); `.staleWhileRevalidate(Duration)`; `.staleIfError(Duration)`; `.noTransform()`. ## Where to apply it 1. **Per handler** — `ResponseEntity.ok().cacheControl(cc).body(x)`. Most common and most granular. 2. **Static resources** — `registry.addResourceHandler("/static/**").addResourceLocations(...).setCacheControl(cc)` inside a `WebMvcConfigurer`. 3. **Broad rules** — a `WebContentInterceptor` with `setCacheControl(cc)` applied across path patterns. ## Critical gotcha `CacheControl` performs **no server-side caching**. It only writes a header describing caching *policy* to clients and proxies. Don't confuse it with Spring's `@Cacheable` / `CacheManager` abstraction, which caches method results in-process (Caffeine/Redis/etc.). They are unrelated: `CacheControl` = HTTP transport caching; `@Cacheable` = application-level result caching. ## Freshness vs validation `max-age` gives **freshness** (reuse without asking). ETag/Last-Modified give **validation** (ask the server "still valid?" and get a cheap `304 Not Modified` if so). Real APIs often combine both: a short `max-age` plus an `ETag`, so caches serve fresh copies for a while and then revalidate cheaply. ## Edge cases - If you set no `Cache-Control`, browser heuristics may cache GETs unpredictably; being explicit is safer. - `private` matters when a response embeds user-specific data behind a shared CDN — otherwise one user's data could be served to another. - `no-store` is stronger than `no-cache`: `no-cache` still allows storage (just mandatory revalidation), which surprises people.

  • What is the difference between no-cache and no-store?
    no-store forbids caches from keeping any copy at all (for sensitive data). no-cache allows storing the response but requires the cache to revalidate with the origin before every reuse — it can still serve a 304-validated copy.
  • Does CacheControl.maxAge cache anything on the server?
    No. It only writes the Cache-Control header telling clients/proxies how long they may reuse the response. Server-side result caching is a separate concern (@Cacheable / CacheManager).

saying these in an interview costs you the question

  • Thinking CacheControl caches responses on the server
  • Believing no-cache means 'do not store' (that's no-store)
  • Using cachePublic() for user-specific responses behind a CDN
  • Hand-concatenating the Cache-Control header string instead of the builder

context

open as a page

How do you serve static files (CSS, JS, images) in Spring MVC, and where does Spring Boot look for them by default?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Put files under src/main/resources/static (or public/resources/META-INF/resources) and Spring Boot serves them automatically at the URL root. To configure manually, override addResourceHandlers in a WebMvcConfigurer.

open as a page

What is UriComponentsBuilder and why use it instead of string concatenation to build URLs?

level: juniorimportance: must knowfreq 32%

basics

~20 s

UriComponentsBuilder is a Spring helper that builds a URI piece by piece — scheme, host, path, query params — and handles proper encoding for you. It avoids bugs from manually gluing strings and forgetting to escape special characters.

open as a page

What is WebMvcConfigurer and how do you use it to customize Spring MVC?

level: juniorimportance: must knowfreq 70%

basics

~10 s

WebMvcConfigurer is an interface with default (empty) callback methods. You create a @Configuration class implementing it and override only the callbacks you need, like addInterceptors or addViewControllers, to tweak MVC without replacing everything.

open as a page

What does ShallowEtagHeaderFilter do, how does it generate an ETag, and what is it NOT good for?

level: middleimportance: must knowfreq 55%

basics

~20 s

It'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.

open as a page

How do you build an absolute Location URL from the current request, e.g. for a 201 Created response?

level: middleimportance: must knowfreq 38%

basics

~10 s

Use ServletUriComponentsBuilder.fromCurrentRequest() (or fromCurrentContextPath()), append the new resource path, expand the id, and call toUri(). It reads the current request's scheme, host, and port so you don't hardcode them.

open as a page

How do you implement conditional GETs with WebRequest.checkNotModified so you can return 304 without doing expensive work?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Inject WebRequest, compute a cheap version marker (Last-Modified timestamp or ETag), and call webRequest.checkNotModified(marker). If it returns true, return null — Spring sends 304 Not Modified. If false, build and return the full 200 response. This lets you skip rendering when unchanged.

open as a page

What happens when you add @EnableWebMvc to a Spring Boot application, and when should you?

level: seniorimportance: must knowfreq 68%

basics

~10 s

@EnableWebMvc turns on Spring's own MVC Java config and disables Boot's WebMvcAutoConfiguration. You then lose Boot's sensible defaults (JSON converters, static resources, error page). In Boot, usually DON'T add it — just implement WebMvcConfigurer.

open as a page

How do you control HTTP caching for static resources in Spring MVC (cache-period, Cache-Control, ETags)?

level: middleimportance: should knowfreq 55%

basics

~10 s

Use setCachePeriod(seconds) or setCacheControl(CacheControl.maxAge(...)) on the resource handler registration to emit Cache-Control/Expires headers. Spring also auto-adds Last-Modified and ETag support for conditional requests.

open as a page

When would you use addViewControllers and addFormatters, and what do they do?

level: middleimportance: should knowfreq 40%

basics

~20 s

addViewControllers maps a URL straight to a view name (or redirect/status) with no controller code — good for simple pages like /login. addFormatters registers Converters/Formatters that turn request strings into typed method arguments, e.g. String to LocalDate.

open as a page

How do Cache-Control freshness and ETag/Last-Modified validation work together, and how would you combine them in an API?

level: seniorimportance: should knowfreq 38%

basics

~20 s

max-age gives freshness: caches reuse the response with no request until it expires. ETag/Last-Modified give validation: after expiry the client asks 'still valid?' and gets a cheap 304 if unchanged. Combine a short max-age with an ETag so caches serve fast, then revalidate cheaply.

open as a page

Compare classpath: and file: resource locations for static content. When would you serve from the filesystem, and what are the security concerns?

level: seniorimportance: should knowfreq 35%

basics

~20 s

classpath: locations are bundled in the jar (immutable, deploy-time). file: locations point to a directory on disk (mutable at runtime), useful for user uploads. Both go through PathResourceResolver, which blocks path traversal outside the configured root.

open as a page

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

level: seniorimportance: should knowfreq 40%

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.

open as a page

How does encoding interact with URI template expansion in UriComponentsBuilder, and why does the order matter?

level: seniorimportance: should knowfreq 28%

basics

~20 s

You must encode after (or as part of) expansion, because the values you substitute may contain reserved characters like & or /. The safest approach is UriComponentsBuilder.encode() with EncodingMode.TEMPLATE_AND_VALUES, which encodes the template literals and then strictly encodes each expanded variable.

open as a page

What is MvcUriComponentsBuilder.fromMethodCall / fromMethodName and why is it useful?

level: seniorimportance: should knowfreq 18%

basics

~10 s

MvcUriComponentsBuilder builds URLs by pointing at a controller method instead of typing its path. fromMethodCall(on(Ctrl.class).getUser(id)) reads the method's @GetMapping and reconstructs the URL, so if you change the mapping the link updates automatically.

open as a page

What is the difference between configureMessageConverters and extendMessageConverters?

level: seniorimportance: should knowfreq 45%

basics

~20 s

configureMessageConverters lets you supply the whole converter list — if you add any, the framework defaults are NOT added. extendMessageConverters runs after defaults are in place, so you keep them and just add or reorder converters. Prefer extend to keep defaults.

open as a page

Design the HTTP caching strategy for a Spring MVC API with a mix of large expensive reports, small dynamic JSON, and per-user data behind a CDN. Which mechanism for each and why?

level: principalimportance: should knowfreq 24%

basics

~20 s

Expensive reports: WebRequest.checkNotModified with a cheap version marker so a 304 skips the work. Small dynamic JSON: ShallowEtagHeaderFilter (zero code, saves bandwidth) plus a short max-age. Per-user data behind a CDN: Cache-Control private/no-store so shared caches never store it.

open as a page

When generating absolute URLs behind a reverse proxy, how do forwarded headers affect ServletUriComponentsBuilder, and what are the security implications?

level: principalimportance: should knowfreq 16%

basics

~20 s

Behind a proxy the app sees the internal host, so generated URLs are wrong unless it reads Forwarded / X-Forwarded-* headers. Registering Spring's ForwardedHeaderFilter makes ServletUriComponentsBuilder use them. But those headers are client-controllable, so only trust them behind a trusted proxy to avoid host-header/link poisoning.

open as a page

How do HandlerInterceptors registered via addInterceptors work, including ordering and lifecycle, and how do they differ from Servlet Filters?

level: principalimportance: should knowfreq 42%

basics

~20 s

addInterceptors registers HandlerInterceptors with preHandle/postHandle/afterCompletion hooks around controller execution. They run inside the DispatcherServlet (know the handler), and execute in registration order. Filters are lower-level Servlet-container components wrapping the whole request, unaware of the specific handler.

open as a page

Design a static-asset cache-busting strategy in Spring MVC using content versioning. Contrast ContentVersionStrategy with FixedVersionStrategy and address URL generation.

level: principalimportance: nice to knowfreq 25%

basics

~10 s

Enable a VersionResourceResolver with addContentVersionStrategy (per-file MD5 hash in the filename) so each file's URL changes only when its content changes. Serve with long, immutable Cache-Control, and generate versioned URLs via ResourceUrlProvider in templates.

open as a page