This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 7.0.9!

Declarative Annotation-based Caching

For caching declaration, Spring’s caching abstraction provides a set of Java annotations:

  • @Cacheable: Triggers cache population.

  • @CacheEvict: Triggers cache eviction.

  • @CachePut: Updates the cache without interfering with the method execution.

  • @Caching: Regroups multiple cache operations to be applied on a method.

  • @CacheConfig: Shares some common cache-related settings at class-level.

The @Cacheable Annotation

As the name implies, you can use @Cacheable to demarcate methods that are cacheable — that is, methods for which the result is stored in the cache so that, on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually invoke the method. In its simplest form, the annotation declaration requires the name of the cache associated with the annotated method, as the following example shows:

@Cacheable("books")
public Book findBook(ISBN isbn) {...}

In the preceding snippet, the findBook method is associated with the cache named books. Each time the method is called, the cache is checked to see whether the invocation has already been run and does not have to be repeated. While in most cases, only one cache is declared, the annotation lets multiple names be specified so that more than one cache is being used. In this case, each of the caches is checked before invoking the method — if at least one cache is hit, the associated value is returned.

All the other caches that do not contain the value are also updated, even though the cached method was not actually invoked.

The following example uses @Cacheable on the findBook method with multiple caches:

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
When @Cacheable is combined with @Retryable, the retry advice is applied outermost, so each retry attempt checks the cache before invoking the method. See Combining @Retryable with @Cacheable for details.

Default Key Generation

Since caches are essentially key-value stores, each invocation of a cached method needs to be translated into a suitable key for cache access. The caching abstraction uses a simple KeyGenerator based on the following algorithm:

  • If no parameters are given, return SimpleKey.EMPTY.

  • If only one parameter is given, return that instance.

  • If more than one parameter is given, return a SimpleKey that contains all parameters.

This approach works well for most use-cases, as long as parameters have natural keys and implement valid hashCode() and equals() methods. If that is not the case, you need to change the strategy.

To provide a different default key generator, you need to implement the org.springframework.cache.interceptor.KeyGenerator interface.

The default key generation strategy changed with the release of Spring 4.0. Earlier versions of Spring used a key generation strategy that, for multiple key parameters, considered only the hashCode() of parameters and not equals(). This could cause unexpected key collisions (see spring-framework#14870 for background). The new SimpleKeyGenerator uses a compound key for such scenarios.

If you want to keep using the previous key strategy, you can configure the deprecated org.springframework.cache.interceptor.DefaultKeyGenerator class or create a custom hash-based KeyGenerator implementation.

Custom Key Generation Declaration

Since caching is generic, the target methods are quite likely to have various signatures that cannot be readily mapped on top of the cache structure. This tends to become obvious when the target method has multiple arguments out of which only some are suitable for caching (while the rest are used only by the method logic). Consider the following example:

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

At first glance, while the two boolean arguments influence the way the book is found, they are no use for the cache. Furthermore, what if only one of the two is important while the other is not?

For such cases, the @Cacheable annotation lets you specify how the key is generated through its key attribute. You can use SpEL to pick the arguments of interest (or their nested properties), perform operations, or even invoke arbitrary methods without having to write any code or implement any interface. This is the recommended approach over the default generator, since methods tend to be quite different in signatures as the code base grows. While the default strategy might work for some methods, it rarely works for all methods.

The following examples use various SpEL declarations (if you are not familiar with SpEL, do yourself a favor and read Spring Expression Language):

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

The preceding snippets show how easy it is to select a certain argument, one of its properties, or even an arbitrary (static) method.

If the algorithm responsible for generating the key is too specific or if it needs to be shared, you can define a custom keyGenerator on the operation. To do so, specify the name of the KeyGenerator bean implementation to use, as the following example shows:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
The key and keyGenerator parameters are mutually exclusive and an operation that specifies both results in an exception.

Default Cache Resolution

The caching abstraction uses a simple CacheResolver that retrieves the caches defined at the operation level by using the configured CacheManager.

To provide a different default cache resolver, you need to implement the org.springframework.cache.interceptor.CacheResolver interface.

Custom Cache Resolution

The default cache resolution fits well for applications that work with a single CacheManager and have no complex cache resolution requirements.

For applications that work with several cache managers, you can set the cacheManager to use for each operation, as the following example shows:

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
1 Specifying anotherCacheManager.

You can also replace the CacheResolver entirely in a fashion similar to that of replacing key generation. The resolution is requested for every cache operation, letting the implementation actually resolve the caches to use based on runtime arguments. The following example shows how to specify a CacheResolver:

@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
1 Specifying the CacheResolver.

Since Spring 4.1, the value attribute of the cache annotations are no longer mandatory, since this particular information can be provided by the CacheResolver regardless of the content of the annotation.

Similarly to key and keyGenerator, the cacheManager and cacheResolver parameters are mutually exclusive, and an operation specifying both results in an exception, as a custom CacheManager is ignored by the CacheResolver implementation. This is probably not what you expect.

Synchronized Caching

In a multi-threaded environment, certain operations might be concurrently invoked for the same argument (typically on startup). By default, the cache abstraction does not lock anything, and the same value may be computed several times, defeating the purpose of caching.

For those particular cases, you can use the sync attribute to instruct the underlying cache provider to lock the cache entry while the value is being computed. As a result, only one thread is busy computing the value, while the others are blocked until the entry is updated in the cache. The following example shows how to use the sync attribute:

@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 Using the sync attribute.
This is an optional feature, and your favorite cache library may not support it. All CacheManager implementations provided by the core framework support it. See the documentation of your cache provider for more details.

Caching with CompletableFuture and Reactive Return Types

As of 6.1, cache annotations take CompletableFuture and reactive return types into account, automatically adapting the cache interaction accordingly.

For a method returning a CompletableFuture, the object produced by that future will be cached whenever it is complete, and the cache lookup for a cache hit will be retrieved via a CompletableFuture:

@Cacheable("books")
public CompletableFuture<Book> findBook(ISBN isbn) {...}

For a method returning a Reactor Mono, the object emitted by that Reactive Streams publisher will be cached whenever it is available, and the cache lookup for a cache hit will be retrieved as a Mono (backed by a CompletableFuture):

@Cacheable("books")
public Mono<Book> findBook(ISBN isbn) {...}

For a method returning a Reactor Flux, the objects emitted by that Reactive Streams publisher will be collected into a List and cached whenever that list is complete, and the cache lookup for a cache hit will be retrieved as a Flux (backed by a CompletableFuture for the cached List value):

@Cacheable("books")
public Flux<Book> findBooks(String author) {...}

Such CompletableFuture and reactive adaptation also works for synchronized caching, computing the value only once in case of a concurrent cache miss:

@Cacheable(cacheNames="foos", sync=true) (1)
public CompletableFuture<Foo> executeExpensiveOperation(String id) {...}
1 Using the sync attribute.
In order for such an arrangement to work at runtime, the configured cache needs to be capable of CompletableFuture-based retrieval. The Spring-provided ConcurrentMapCacheManager automatically adapts to that retrieval style, and CaffeineCacheManager natively supports it when its asynchronous cache mode is enabled: set setAsyncCacheMode(true) on your CaffeineCacheManager instance.
@Bean
CacheManager cacheManager() {
	CaffeineCacheManager cacheManager = new CaffeineCacheManager();
	cacheManager.setCacheSpecification(...);
	cacheManager.setAsyncCacheMode(true);
	return cacheManager;
}

Last but not least, be aware that annotation-driven caching is not appropriate for sophisticated reactive interactions involving composition and back pressure. If you choose to declare @Cacheable on specific reactive methods, consider the impact of the rather coarse-granular cache interaction which simply stores the emitted object for a Mono or even a pre-collected list of objects for a Flux.

Conditional Caching

Sometimes, a method might not be suitable for caching all the time (for example, it might depend on the given arguments). The cache annotations support such use cases through the condition parameter, which takes a SpEL expression that is evaluated to either true or false. If true, the method is cached. If not, it behaves as if the method is not cached (that is, the method is invoked every time no matter what values are in the cache or what arguments are used). For example, the following method is cached only if the argument name has a length shorter than 32:

@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)