JCache (JSR-107) Annotations

Since version 4.1, Spring’s caching abstraction fully supports the JCache standard (JSR-107) annotations: @CacheResult, @CachePut, @CacheRemove, and @CacheRemoveAll as well as the @CacheDefaults, @CacheKey, and @CacheValue companions. You can use these annotations even without migrating your cache store to JSR-107. The internal implementation uses Spring’s caching abstraction and provides default CacheResolver and KeyGenerator implementations that are compliant with the specification. In other words, if you are already using Spring’s caching abstraction, you can switch to these standard annotations without changing your cache storage (or configuration, for that matter).

Feature Summary

For those who are familiar with Spring’s caching annotations, the following table describes the main differences between the Spring annotations and their JSR-107 counterparts:

Table 1. Spring vs. JSR-107 caching annotations
Spring JSR-107 Remark

@Cacheable

@CacheResult

Fairly similar. @CacheResult can cache specific exceptions and force the execution of the method regardless of the content of the cache.

@CachePut

@CachePut

While Spring updates the cache with the result of the method invocation, JCache requires that it be passed it as an argument that is annotated with @CacheValue. Due to this difference, JCache allows updating the cache before or after the actual method invocation.

@CacheEvict

@CacheRemove

Fairly similar. @CacheRemove supports conditional eviction when the method invocation results in an exception.

@CacheEvict(allEntries=true)

@CacheRemoveAll

See @CacheRemove.

@CacheConfig

@CacheDefaults

Lets you configure the same concepts, in a similar fashion.

JCache has the notion of javax.cache.annotation.CacheResolver, which is identical to the Spring’s CacheResolver interface, except that JCache supports only a single cache. By default, a simple implementation retrieves the cache to use based on the name declared on the annotation. It should be noted that, if no cache name is specified on the annotation, a default is automatically generated. See the javadoc of @CacheResult#cacheName() for more information.

CacheResolver instances are retrieved by a CacheResolverFactory. It is possible to customize the factory for each cache operation, as the following example shows:

@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) (1)
public Book findBook(ISBN isbn)
1 Customizing the factory for this operation.
For all referenced classes, Spring tries to locate a bean with the given type. If more than one match exists, a new instance is created and can use the regular bean lifecycle callbacks, such as dependency injection.

Keys are generated by a javax.cache.annotation.CacheKeyGenerator that serves the same purpose as Spring’s KeyGenerator. By default, all method arguments are taken into account, unless at least one parameter is annotated with @CacheKey. This is similar to Spring’s custom key generation declaration . For instance, the following are identical operations, one using Spring’s abstraction and the other using JCache:

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

@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)

You can also specify the CacheKeyResolver on the operation, similar to how you can specify the CacheResolverFactory.

JCache can manage exceptions thrown by annotated methods. This can prevent an update of the cache, but it can also cache the exception as an indicator of the failure instead of calling the method again. Assume that InvalidIsbnNotFoundException is thrown if the structure of the ISBN is invalid. This is a permanent failure (no book could ever be retrieved with such a parameter). The following caches the exception so that further calls with the same, invalid, ISBN throw the cached exception directly instead of invoking the method again:

@CacheResult(cacheName="books", exceptionCacheName="failures"
			cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)

Enabling JSR-107 Support

You do not need to do anything specific to enable the JSR-107 support alongside Spring’s declarative annotation support. Both @EnableCaching and the cache:annotation-driven XML element automatically enable the JCache support if both the JSR-107 API and the spring-context-support module are present in the classpath.

Depending on your use case, the choice is basically yours. You can even mix and match services by using the JSR-107 API on some and using Spring’s own annotations on others. However, if these services impact the same caches, you should use a consistent and identical key generation implementation.