Spring Boot with Redis
IntermediateSpring Data Redis provides RedisTemplate and the @Cacheable abstraction over Redis; cache-aside, write-through, and pub/sub patterns are all supported out of the box.
Overview
Spring Boot's `spring-boot-starter-data-redis` auto-configures a `RedisTemplate` (low-level key-value operations) and a `StringRedisTemplate` using Lettuce as the default async client. The most common use cases are: **caching** (`@Cacheable` with `RedisCacheManager`), **session storage** (`spring-session-data-redis` for distributed sessions), **rate limiting** (atomic counters with TTL), **pub/sub** (`RedisMessageListenerContainer`), and **distributed locks** (Redisson or `SET NX PX` pattern). Redis's single-threaded execution and atomic operations make it ideal for distributed coordination without transaction overhead.
@Cacheable with RedisCacheManager
Replace an in-process Caffeine/Ehcache with Redis by configuring `RedisCacheManager` as the `CacheManager` bean. `@Cacheable` checks the Redis cache before executing the method; `@CacheEvict` removes an entry; `@CachePut` always writes. Use a `RedisCacheConfiguration` to set TTL, key prefix, and JSON serialisation.
@Configuration
class RedisCacheConfig {
@Bean
CacheManager cacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration cfg = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.prefixCacheNameWith("myapp::")
.serializeValuesWith( // JSON instead of Java serialisation
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(cf)
.cacheDefaults(cfg)
.withCacheConfiguration("products", cfg.entryTtl(Duration.ofHours(1)))
.build();
}
}
@Service
class ProductService {
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) { return productRepo.findById(id).orElseThrow(); }
@CacheEvict(value = "products", key = "#product.id")
public Product update(Product product) { return productRepo.save(product); }
@CacheEvict(value = "products", allEntries = true)
public void refreshAll() { /* clear all cache entries */ }
}RedisTemplate for Low-Level Operations
`RedisTemplate` gives direct access to Redis data structures: strings, hashes, lists, sets, sorted sets. Use `opsForValue()` for simple string key-value, `opsForHash()` for field maps, `opsForZSet()` for leaderboards. Configure with `StringRedisSerializer` for keys and `GenericJackson2JsonRedisSerializer` for values to keep Redis keys readable.
@Service
@RequiredArgsConstructor
class RateLimiter {
private final RedisTemplate<String, String> redis;
public boolean isAllowed(String userId, int maxPerMinute) {
String key = "rate:" + userId + ":" + Instant.now().truncatedTo(ChronoUnit.MINUTES);
Long count = redis.opsForValue().increment(key);
if (count == 1) redis.expire(key, Duration.ofMinutes(2)); // set TTL on first increment
return count <= maxPerMinute;
}
}
@Service
@RequiredArgsConstructor
class SessionCache {
private final RedisTemplate<String, Object> redis;
public void save(String sessionId, Map<String, Object> data) {
redis.opsForHash().putAll("session:" + sessionId, data);
redis.expire("session:" + sessionId, Duration.ofHours(1));
}
public Object get(String sessionId, String field) {
return redis.opsForHash().get("session:" + sessionId, field);
}
}Pub/Sub and Distributed Lock Pattern
Redis pub/sub broadcasts messages to all subscribers on a channel — useful for cache invalidation across pods. For distributed locks, use the `SET key value NX PX ttl` pattern (set if not exists, with TTL to prevent deadlocks). Redisson's `RLock` implements the full Redlock algorithm for multi-node safety.
// Pub/Sub — cache invalidation broadcast
@Configuration
class RedisPubSubConfig {
@Bean
RedisMessageListenerContainer listenerContainer(
RedisConnectionFactory cf, MessageListener invalidationListener) {
var container = new RedisMessageListenerContainer();
container.setConnectionFactory(cf);
container.addMessageListener(invalidationListener,
new PatternTopic("cache-invalidation:*"));
return container;
}
}
@Component
class CacheInvalidationListener implements MessageListener {
@Override
public void onMessage(Message msg, byte[] pattern) {
String key = new String(msg.getBody());
cacheManager.getCache("products").evict(key);
}
}
// Simple distributed lock (SET NX PX)
public boolean tryLock(String lockKey, String owner, Duration ttl) {
Boolean acquired = redis.opsForValue()
.setIfAbsent(lockKey, owner, ttl); // SET lockKey owner NX PX ttl
return Boolean.TRUE.equals(acquired);
}
public void unlock(String lockKey, String owner) {
// Lua script: atomic check-and-delete
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
"return redis.call('del',KEYS[1]) else return 0 end";
redis.execute(new DefaultRedisScript<>(script, Long.class),
List.of(lockKey), owner);
}Key Points to Remember
- 1@Cacheable with RedisCacheManager makes method results Redis-backed with per-cache TTL
- 2Use GenericJackson2JsonRedisSerializer for human-readable JSON values in Redis
- 3RedisTemplate.opsForValue().increment() + expire() implements atomic rate limiting
- 4SET key value NX PX ttl is the foundation of a Redis distributed lock
- 5Redis pub/sub broadcasts to all subscribers — useful for cache invalidation across pods
- 6spring-session-data-redis stores HTTP session in Redis — enables stateless horizontally-scaled apps
Interview Questions
Sign in to ask AriaHow does @Cacheable with RedisCacheManager differ from an in-process cache?
How would you implement a rate limiter using Redis in Spring Boot?
Why do you need a Lua script for the distributed lock unlock operation?
What is the risk of not setting a TTL on a Redis lock key?
How would you use Redis pub/sub to invalidate a local cache across multiple pods?
Ask Aria about Spring Boot with Redis
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.