Backend Development 19 min read

Spring Boot Cache: JCache Specification, Cache Abstraction, Annotations, and Redis Integration

This article explains Spring Boot caching by introducing the JSR‑107 JCache specification, detailing core cache interfaces, showing the Spring Cache abstraction and its implementations, demonstrating cache annotations such as @Cacheable, @CachePut and @CacheEvict, and finally covering Redis setup and custom CacheManager usage.

Top Architect
Top Architect
Top Architect
Spring Boot Cache: JCache Specification, Cache Abstraction, Annotations, and Redis Integration

Spring Boot caching relies on the JSR‑107 (JCache) specification, which defines standard cache interfaces such as CachingProvider , CacheManager , Cache , Entry and Expiry . The article first describes these interfaces and the role of JCache in Java.

JCache

JSR‑107 (JCache) provides a unified API for cache operations like get, put, evict, and expiration handling across different cache providers.

1. JCache Core Interfaces

A CachingProvider can create and manage multiple CacheManager instances.

Each CacheManager manages uniquely named Cache objects.

Cache behaves like a map storing key‑value Entry objects.

Expiry defines the lifetime of a cache entry.

2. Spring Cache Abstraction

Since Spring 3.1, Spring provides a cache abstraction with org.springframework.cache.Cache and org.springframework.cache.CacheManager . The Cache interface defines operations such as get , put , evict , and clear . Implementations include RedisCache , EhCacheCache , and ConcurrentMapCache .

Cache Interface

public interface Cache {
    String getName();
    Object getNativeCache();
    @Nullable
    Cache.ValueWrapper get(Object key);
    @Nullable
T get(Object key, @Nullable Class
type);
    @Nullable
T get(Object key, @Nullable Callable
valueLoader);
    void put(Object key, @Nullable Object value);
    @Nullable
    default ValueWrapper putIfAbsent(Object key, @Nullable Object value) { ... }
    void evict(Object key);
    default boolean evictIfPresent(Object key) { ... }
    void clear();
    default boolean invalidate() { ... }
    class ValueRetrievalException extends RuntimeException { ... }
    @FunctionalInterface
    interface ValueWrapper { @Nullable Object get(); }
}

The Cache interface abstracts cache operations, while the ValueWrapper provides a simple wrapper for cached values.

AbstractValueAdaptingCache

public abstract class AbstractValueAdaptingCache implements Cache {
    private final boolean allowNullValues;
    protected AbstractValueAdaptingCache(boolean allowNullValues) { this.allowNullValues = allowNullValues; }
    public final boolean isAllowNullValues() { return this.allowNullValues; }
    @Nullable
    public ValueWrapper get(Object key) { return toValueWrapper(lookup(key)); }
    @Nullable
    public
T get(Object key, @Nullable Class
type) { ... }
    @Nullable
    protected abstract Object lookup(Object key);
    @Nullable
    protected Object fromStoreValue(@Nullable Object storeValue) { ... }
    protected Object toStoreValue(@Nullable Object userValue) { ... }
    @Nullable
    protected ValueWrapper toValueWrapper(@Nullable Object storeValue) { ... }
}

This abstract class handles null‑value semantics for caches, allowing or rejecting null entries based on the allowNullValues flag.

ConcurrentMapCache

public class ConcurrentMapCache extends AbstractValueAdaptingCache {
    private final String name;
    private final ConcurrentMap
store;
    private final SerializationDelegate serialization;
    public ConcurrentMapCache(String name) { this(name, new ConcurrentHashMap(256), true); }
    public ConcurrentMapCache(String name, boolean allowNullValues) { this(name, new ConcurrentHashMap(256), allowNullValues); }
    protected ConcurrentMapCache(String name, ConcurrentMap
store, boolean allowNullValues, @Nullable SerializationDelegate serialization) { ... }
    public final String getName() { return this.name; }
    public final ConcurrentMap
getNativeCache() { return this.store; }
    @Nullable
    protected Object lookup(Object key) { return this.store.get(key); }
    public
T get(Object key, Callable
valueLoader) { ... }
    public void put(Object key, @Nullable Object value) { this.store.put(key, toStoreValue(value)); }
    public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { ... }
    public void evict(Object key) { this.store.remove(key); }
    public boolean evictIfPresent(Object key) { return this.store.remove(key) != null; }
    public void clear() { this.store.clear(); }
    public boolean invalidate() { boolean notEmpty = !this.store.isEmpty(); this.store.clear(); return notEmpty; }
    protected Object toStoreValue(@Nullable Object userValue) { ... }
    protected Object fromStoreValue(@Nullable Object storeValue) { ... }
}

ConcurrentMapCache is Spring's default in‑memory cache implementation, optionally supporting value copying via a SerializationDelegate .

CacheManager Interface

public interface CacheManager {
    @Nullable Cache getCache(String name);
    Collection
getCacheNames();
}

Implementations such as ConcurrentMapCacheManager , SimpleCacheManager , and RedisCacheManager manage groups of caches identified by names.

3. Spring Cache Annotations

The article explains the use of @EnableCaching to activate annotation‑driven caching and details the three main method‑level annotations:

@Cacheable – caches the result of a method call.

@CachePut – always executes the method and updates the cache.

@CacheEvict – removes entries from the cache.

It also covers advanced annotations @Caching (combining multiple cache operations) and @CacheConfig (class‑level default cache settings).

Example of @Cacheable

@Cacheable(cacheNames = {"emp"}, key = "#id", condition = "#id > 0", unless = "#result == null")
public Employee getEmp(Integer id) {
    Employee emp = employeeMapper.getEmpById(id);
    return emp;
}

Example of @CachePut

@CachePut(value = "emp", key = "#result.id")
public Employee updateEmp(Employee employee) {
    employeeMapper.updateEmp(employee);
    return employee;
}

Example of @CacheEvict

@CacheEvict(value = "emp", key = "#id", allEntries = true)
public void deleteEmp(Integer id) {
    employeeMapper.deleteEmpById(id);
}

4. Redis Integration

The default Spring Boot cache uses ConcurrentMapCache . For production, Redis is commonly used. The article shows how to start a Redis container with Docker, connect using Redis Desktop Manager, and use StringRedisTemplate and RedisTemplate for various data structures (String, List, Set, Hash, ZSet).

Redis Test Code

public class RedisTest {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedisTemplate redisTemplate;
    public void test01() {
        stringRedisTemplate.opsForValue().append("msg", "hello");
        String s = stringRedisTemplate.opsForValue().get("msg");
        stringRedisTemplate.opsForList().leftPush("mylist", "1");
    }
    public void test02() {
        Employee emp = new Employee();
        redisTemplate.opsForValue().set("emp-01", emp);
    }
}

To store objects as JSON, the article suggests customizing RedisTemplate with a Jackson2JsonRedisSerializer or converting objects to JSON manually.

Custom RedisTemplate Example

public class MyRedisConfig {
    @Bean
    public RedisTemplate
empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate
template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer
ser = new Jackson2JsonRedisSerializer<>(Employee.class);
        template.setDefaultSerializer(ser);
        return template;
    }
}

Custom CacheManager for Redis

@Bean
public RedisCacheManager employeeCacheManager(RedisTemplate
employeeRedisTemplate) {
    RedisCacheManager cacheManager = new RedisCacheManager(employeeRedisTemplate);
    cacheManager.setUsePrefix(true);
    return cacheManager;
}

This custom RedisCacheManager allows prefixing cache names and using a JSON‑based RedisTemplate for serialization.

Overall, the article provides a comprehensive guide to Spring Boot caching, from the JCache specification to practical Redis integration, and demonstrates how to configure and use cache annotations effectively.

BackendJavaCacheRedisSpring BootannotationsJCache
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.