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 WebFlux.

CacheControl

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

  • Controllers

  • Static resources

Although RFC 7234 describes all possible directives for Cache-Control response header 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);
/// Preventing 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)
/// Preventing 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()

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(ServerWebExchange exchange, Model model) {
long eTag = ... 
if (exchange.checkNotModified(eTag)) {
return null; 
}
model.addAttribute(...); 
return "myViewName";
}
  1. Application-specific calculation.
  2. The response was set to 304 (NOT_MODIFIED). No further processing occurs.
  3. Continue processing the request.
Kotlin
@RequestMapping
fun myHandleMethod (exchange: ServerWebExchange, model: Model): String? {
val eTag: Long = ... 
if (exchange.checkNotModified(eTag)) {
return null
}
model.addAttribute(...) 
return "myViewName"
}
  1. Application-specific calculation.
  2. The response was set to 304 (NOT_MODIFIED). No further processing occurs.
  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. See the section on configuring static resources.