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:
-
Controllers
-
Static resources
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:
// 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();
// 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 aCache-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 forn
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:
@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);
}
@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:
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {
long eTag = ...
if (request.checkNotModified(eTag)) {
return null;
}
model.addAttribute(...);
return "myViewName";
}
- Application-specific calculation.
- Response was set to 304 (NOT_MODIFIED) - no further processing allowed.
- Continue processing the request.
@RequestMapping
fun myHandleMethod(request: WebRequest, model: Model): String? {
val eTag: Long = ...
if (request.checkNotModified(eTag)) {
return null
}
model[...] = ...
return "myViewName"
}
- Application-specific calculation.
- Response was set to 304 (NOT_MODIFIED) - no further processing allowed.
- 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.