HTTP caching can significantly improve the performance of a web application. HTTP caching is built around the Cache-Control response header and subsequently conditional request headers (such as Last-Modified and ETag). Cache-Control tells private (eg browser) and public (eg proxy) caches how to cache and reuse responses. The ETag header is used for a conditional request, which may result in a 304 (NOT_MODIFIED) response without a body if the contents have not changed. ETag can be considered a more advanced successor to the Last-Modified header.

This section describes the HTTP caching-related options available in Spring Web MVC.

CacheControl

CacheControl provides support for configuring options associated with the Cache-Control header and is accepted as an argument in a number of places:

Whereas RFC 7234 describes all possible directives for the header response Cache-Control, type CacheControl takes a use-case driven approach that focuses on common scenarios:

Java
// Caching for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store".
CacheControl ccNoStore = CacheControl.noStore();
// Caching for ten days in public and private caches,
// public cache should not convert the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
Kotlin
// Caching for an hour - "Cache-Control: max-age=3600"
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)
// Prevent caching - "Cache-Control: no-store".
val ccNoStore = CacheControl.noStore()
// Caching for ten days in public and private caches,
// public cache should not convert the response
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()

WebContentGenerator also accepts a simpler cachePeriod property (defined in seconds) that works like this:

  • The value -1 does not generate a Cache-Control response header.

  • The value 0 prevents caching using the "Cache-Control: no-store" directive.

  • When the value is n > 0 The provided response is cached for n seconds using the "Cache-Control: max-age=n" directive.

Controllers

Controllers can add explicit support for HTTP caching. We recommend doing this because the lastModified or ETag value for a resource must be calculated before it can be compared with conditional request headers. The controller can add the ETag header and Cache-Control parameters to the ResponseEntity, as shown in the following example:

Java
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
    Book book = findBook(id);
    String version = book.getVersion();
    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book);
}
Kotlin
@GetMapping("/book/{id}")
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {
    val book = findBook(id);
    val version = book.getVersion()
    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book)
}

In the previous example, a 304 response (NOT_MODIFIED) is sent with an empty body if a comparison with the conditional request headers indicates that the contents have not changed. Otherwise, the ETag and Cache-Control headers are added to the response.

You can also check for conditional request headers in the controller, as shown in the following example:

Java
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {
    long eTag = ...
    if (request.checkNotModified(eTag)) {
        return null; 
    }
    model.addAttribute(...); 
    return "myViewName";
}
  1. Application-specific calculation.
  2. Response was set to 304 (NOT_MODIFIED) - no further processing allowed.
  3. Continue processing the request.
Kotlin
@RequestMapping
fun myHandleMethod(request: WebRequest, model: Model): String? {
    val eTag: Long = ...
    if (request.checkNotModified(eTag)) {
        return null 
    }
    model[...] = ... 
    return "myViewName"
}
  1. Application-specific calculation.
  2. Response was set to 304 (NOT_MODIFIED) - no further processing allowed.
  3. Continue processing the request.

There are three options for checking conditional requests against eTag values, lastModified values, or both. For conditional GET and HEAD requests, you can set the response to 304 (NOT_MODIFIED). For conditional POST, PUT and DELETE, you can instead set the response to 412 (PRECONDITION_FAILED) to prevent simultaneous modification.

Static resources

For optimal performance, static resources should be handled using Cache-Control and conditional response headers.

Filter ETag

ShallowEtagHeaderFilter can be used to add "shallow" eTag values that are calculated from the response content and thus save bandwidth but not CPU time.