HTTP Caching

HTTP caching can significantly improve the performance of a web application. HTTP caching revolves around the Cache-Control response header and subsequent conditional request headers, such as Last-Modified and ETag. Cache-Control advises private (for example, browser) and public (for example, proxy) caches how to cache and re-use responses. An ETag header is used to make a conditional request that may result in a 304 (NOT_MODIFIED) without a body, if the content has not changed. ETag can be seen as a more sophisticated successor to the Last-Modified header.

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

CacheControl

CacheControl provides support for configuring settings related to the Cache-Control header and is accepted as an argument in a number of places:

While RFC 7234 describes all possible directives for the Cache-Control response header, the CacheControl type takes a use case-oriented approach that focuses on the common scenarios, as the following example shows:

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

Controllers

Controllers can add explicit support for HTTP caching. We recommend doing so, since the lastModified or ETag value for a resource needs to be calculated before it can be compared against conditional request headers. A controller can add an ETag and Cache-Control settings to a ResponseEntity, as the following example shows:

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

The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison to the conditional request headers indicates the content has not changed. Otherwise, the ETag and Cache-Control headers are added to the response.

You can also make the check against conditional request headers in the controller, as the following example shows:

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

	long eTag = ... (1)

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

	model.addAttribute(...); (3)
	return "myViewName";
}
1 Application-specific calculation.
2 Response has been set to 304 (NOT_MODIFIED). No further processing.
3 Continue with request processing.
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 Application-specific calculation.
2 Response has been set to 304 (NOT_MODIFIED). No further processing.
3 Continue with request processing.

There are three variants 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 409 (PRECONDITION_FAILED) to prevent concurrent modification.

Static Resources

You should serve static resources with a Cache-Control and conditional response headers for optimal performance. See the section on configuring webflux-config-static-resources.