Topic 8.2
Spring Boot, RedisTemplate, and Spring Cache
In one line
Spring Data Redis gives you StringRedisTemplate/RedisTemplate for direct access and the Spring Cache abstraction (@Cacheable, @CachePut, @CacheEvict) backed by RedisCacheManager. Configure serializers and TTLs explicitly, and know the proxy pitfalls that make annotations silently do nothing.
Think of it like this
An office assistant who automatically files copies of every report you produce (@Cacheable). Handy, until you discover they only notice reports handed to them through the front desk; reports you pass to yourself (self-invocation) never get filed.
Key ideas
- 01
StringRedisTemplateuses string serializers for keys and values, readable inredis-cli, and the right default. The plainRedisTemplate<Object,Object>defaults to JDK serialization: binary blobs, unreadable by other languages, fragile across class changes, and a deserialization security risk. Always configure serializers explicitly. - 02
Enable caching with
@EnableCachingand aRedisCacheManagerwithentryTtl, a key prefix, JSON value serialization, and per-cache TTL overrides. Without a TTL, Spring's Redis cache entries never expire by default. - 03
Annotations:
@Cacheable(cacheNames="product", key="#id")reads-through;@CachePutalways runs the method and writes the result;@CacheEvictremoves entries (allEntries=truescans the cache's keys, which is expensive on big caches);@Cachingcombines several. - 04
Proxy pitfalls: annotations work through Spring AOP proxies. Calling a
@Cacheablemethod from another method in the same class bypasses the proxy, so no caching happens. Private methods aren't proxied. Put cached methods in a separate bean. - 05
@Cacheable(sync = true)makes concurrent misses for the same key in one JVM wait for one load, a local stampede guard only. Multiple instances still each load (Topic 6.5). - 06
Spring Session Data Redis stores HTTP sessions in Redis with
@EnableRedisHttpSession(or Spring Boot auto-config), giving stateless app instances (Topic 8.4).
Code & diagrams
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
RedisCacheManager cacheManager(RedisConnectionFactory cf, ObjectMapper mapper) {
var json = new Jackson2JsonRedisSerializer<>(mapper, Object.class); // no default typing
RedisCacheConfiguration base = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.prefixCacheNameWith("app:v3:") // versioned namespace
.disableCachingNullValues()
.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(SerializationPair.fromSerializer(json));
return RedisCacheManager.builder(cf)
.cacheDefaults(base)
.withCacheConfiguration("product", base.entryTtl(Duration.ofMinutes(30)))
.withCacheConfiguration("pricing", base.entryTtl(Duration.ofSeconds(30)))
.build();
}
}Typed JSON per cache is safer than polymorphic typing; here the method returns a concrete DTO.
@Service
public class ProductQueries {
@Cacheable(cacheNames = "product", key = "#id", sync = true)
public ProductDto byId(long id) {
return repo.findDtoById(id).orElseThrow();
}
@CacheEvict(cacheNames = "product", key = "#id")
public void onProductChanged(long id) { }
// BUG: self-invocation bypasses the proxy, so this never uses the cache.
public List<ProductDto> byIds(List<Long> ids) {
return ids.stream().map(this::byId).toList();
}
}127.0.0.1:6379> KEYS app:v3:product* # lab only!
1) "app:v3:product::42"
127.0.0.1:6379> GET app:v3:product::42
"{\"id\":42,\"name\":\"Laptop Pro 14\",\"price\":1299}"
127.0.0.1:6379> TTL app:v3:product::42
(integer) 1793Interview problem
The problem
@Cacheable isn't caching and the cache never expires
A team added @Cacheable to product lookups. Redis shows binary keys and values, entries never expire, a batch method gets no cache hits at all, and after a deploy some instances throw SerializationException. Fix all four problems.
The interviewer follows up
What does @CacheEvict(allEntries = true) do on Redis?
When it breaks
JDK serialization in shared caches
What you see
Adding a field changes serialVersionUID; instances on the old version can't read new entries (and vice versa) during a rolling deploy. It's also an unsafe deserialization surface.
Fix & prevent
JSON (or Protobuf) with explicit DTOs, versioned cache prefixes, and no default typing.
Explain it without notes
Why doesn't a @Cacheable method cache when called from the same class?
Practice
Configure RedisCacheManager with JSON values and per-cache TTLs, then verify in redis-cli that keys are readable and have TTLs.
Trade-offs
- ↔
Spring Cache annotations are quick to adopt but hide cache behaviour; direct
RedisTemplatecode is more verbose and explicit about keys, TTLs and batching.
Done when you can
I configure serializers, TTLs and prefixes explicitly in Spring.
I can spot self-invocation and other proxy pitfalls.
I know what
sync = truedoes and doesn't do.