Spring Web Reactive | 1. Spring WebFlux | 1.10. HTTP Caching

Web MVC

HTTP caching can significantly improve web application performance. It revolves around the Cache-Control response header and conditional request headers such as Last-Modified and ETag. Cache-Control advises private caches, such as browsers, and public caches, such as proxies, how to cache and reuse responses. The ETag header enables a conditional request that returns 304 (NOT_MODIFIED) without a body if the content has not changed. It can be considered a more precise alternative to Last-Modified.

This section describes the HTTP caching options available in Spring WebFlux.

1.10.1. CacheControl

Web MVC

CacheControl supports configuring settings for the Cache-Control header and is accepted as an argument in several places:

RFC 7234 describes all possible Cache-Control response header directives. CacheControl takes a use-case-oriented approach focused on common scenarios.

Java

// Cache 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();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();

Kotlin

// Cache 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()

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()

1.10.2. Controllers

Web MVC

Controllers can add explicit support for HTTP caching. This approach is recommended because the resource’s lastModified or ETag value must be calculated before comparison with conditional request headers. You can add ETag and Cache-Control settings to a ResponseEntity.

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)
}

If the content has not changed according to the conditional request headers, the example sends a 304 (NOT_MODIFIED) response with an empty body. Otherwise, it adds ETag and Cache-Control headers to the response.

You can also check conditional request headers in a controller.

Java

@RequestMapping
public String myHandleMethod(ServerWebExchange exchange, Model model) {

    long eTag = ...   // (1)

    if (exchange.checkNotModified(eTag)) {
        return null;   // (2)
    }

    model.addAttribute(...);   // (3)
    return "myViewName";
}

Kotlin

@RequestMapping
fun myHandleMethod(exchange: ServerWebExchange, model: Model): String? {

    val eTag: Long = ...   // (1)

    if (exchange.checkNotModified(eTag)) {
        return null  // (2)
    }

    model.addAttribute(...)   // (3)
    return "myViewName"
}
  • (1) Calculate it using application-specific logic.
  • (2) Set the response to 304 (NOT_MODIFIED) and stop processing.
  • (3) Continue request processing.

There are three variants for checking conditional requests against an eTag, a lastModified value, or both. Conditional GET and HEAD requests can return 304 (NOT_MODIFIED). Conditional POST, PUT, and DELETE requests can instead return 412 (PRECONDITION_FAILED) to prevent concurrent modifications.

1.10.3. Static Resources

Web MVC

For optimal performance, serve static resources with Cache-Control and conditional response headers. See the static resources configuration section.