Command Palette

Search for a command to run...

Hectal
PHASE 8Intermediate ~9 min· topic 2 of 6

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.

0/6 · 0%

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

  1. 01

    StringRedisTemplate uses string serializers for keys and values, readable in redis-cli, and the right default. The plain RedisTemplate<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.

  2. 02

    Enable caching with @EnableCaching and a RedisCacheManager with entryTtl, a key prefix, JSON value serialization, and per-cache TTL overrides. Without a TTL, Spring's Redis cache entries never expire by default.

  3. 03

    Annotations: @Cacheable(cacheNames="product", key="#id") reads-through; @CachePut always runs the method and writes the result; @CacheEvict removes entries (allEntries=true scans the cache's keys, which is expensive on big caches); @Caching combines several.

  4. 04

    Proxy pitfalls: annotations work through Spring AOP proxies. Calling a @Cacheable method 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.

  5. 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).

  6. 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

CacheConfig.javajava
@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();
    }
}
ProductQueries.javajava

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();
    }
}
redis-cli-view.redisredis
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) 1793

Interview 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

01

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

01

Why doesn't a @Cacheable method cache when called from the same class?

Practice

01

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 RedisTemplate code 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 = true does and doesn't do.