Spring Boot Tutorial
Redis with Spring Boot
Redis is an in-memory data store used in almost every large web system. It is extremely fast and supports rich data structures — strings, hashes, lists, sets, sorted sets, streams — which makes it useful far beyond caching: rate limiting, leaderboards, distributed locks, session storage, queues and real-time pub/sub.
This lesson connects Spring Boot to Redis with Spring Data Redis, uses StringRedisTemplate and RedisTemplate for data structures, configures Redis as a distributed cache with per-cache TTLs, stores HTTP sessions in Redis, implements a rate limiter and a leaderboard, and uses pub/sub messaging.
Setup
Add spring-boot-starter-data-redis, which uses the Lettuce client. Configure spring.data.redis.host, port, password (or url) and SSL for managed services such as AWS ElastiCache, Azure Cache or Redis Cloud. With Docker Compose or Testcontainers, a redis service is wired automatically through service connections. Spring Boot auto-configures RedisConnectionFactory, StringRedisTemplate and RedisTemplate.
Templates and Serialization
StringRedisTemplate stores keys and values as plain strings — readable with redis-cli and a good default. RedisTemplate<String, Object> can store objects, but its default JDK serialization produces unreadable binary data tied to Java class versions; configure a JSON serializer instead. opsForValue(), opsForHash(), opsForList(), opsForSet() and opsForZSet() map to Redis commands.
Redis as a Distributed Cache
With the Redis starter on the classpath and @EnableCaching, spring.cache.type=redis makes Spring's cache annotations use Redis. Set a default spring.cache.redis.time-to-live and use a RedisCacheManagerBuilderCustomizer for per-cache TTLs and JSON serialization. All application instances now share one cache, and it survives restarts.
Common Patterns
Redis primitives make several distributed patterns simple:
- Rate limiting —
INCRa per-user key with an expiry per time window. - Leaderboards — sorted sets (
ZINCRBY,ZREVRANGE) keep scores ordered. - Sessions — Spring Session stores
HttpSessiondata in Redis so any instance can serve any user. - Distributed locks —
SET key value NX PX; for production use a library such as ShedLock or Redisson. - Pub/sub and streams — lightweight messaging between instances.
Operational Notes
Redis keeps data in memory: set maxmemory and an eviction policy, always put expiries on cache-like keys, and use key prefixes (webnest:ratelimit:user:42) to keep data organised. Avoid the KEYS command in production — it blocks the server; use SCAN.
Examples
Setup with Docker Compose and properties
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# compose.yaml
services:
redis:
image: redis:8
ports:
- "6379"
# application-prod.yml
spring:
data:
redis:
host: ${REDIS_HOST}
port: 6379
password: ${REDIS_PASSWORD}
ssl:
enabled: true
timeout: 2s
(dev) Container webnest-redis-1 Started — connection details applied automatically
redis-cli PING -> PONG
Rate limiting and a leaderboard with StringRedisTemplate
@Service
public class RedisPatterns {
private final StringRedisTemplate redis;
public RedisPatterns(StringRedisTemplate redis) {
this.redis = redis;
}
/** Allow at most 'limit' requests per user per minute. */
public boolean allow(String userId, int limit) {
String key = "webnest:ratelimit:" + userId + ":" + (System.currentTimeMillis() / 60_000);
Long count = redis.opsForValue().increment(key);
if (count != null && count == 1) {
redis.expire(key, Duration.ofMinutes(1));
}
return count != null && count <= limit;
}
public void addPoints(String student, int points) {
redis.opsForZSet().incrementScore("webnest:leaderboard", student, points);
}
public List<String> top(int n) {
Set<ZSetOperations.TypedTuple<String>> top =
redis.opsForZSet().reverseRangeWithScores("webnest:leaderboard", 0, n - 1);
return top.stream().map(t -> t.getValue() + "=" + t.getScore().intValue()).toList();
}
}
allow("42", 3) x5 -> true, true, true, false, false
addPoints("asha", 120); addPoints("ravi", 95); addPoints("asha", 30)
top(2) -> [asha=150, ravi=95]
redis-cli> ZREVRANGE webnest:leaderboard 0 -1 WITHSCORES
1) "asha" 2) "150" 3) "ravi" 4) "95"
Redis cache with JSON values and per-cache TTLs
# application.yml
spring:
cache:
type: redis
redis:
time-to-live: 10m
key-prefix: "webnest:cache:"
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
RedisCacheManagerBuilderCustomizer redisCaches() {
RedisCacheConfiguration json = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(RedisSerializer.json()));
return builder -> builder
.withCacheConfiguration("courseBySlug", json.entryTtl(Duration.ofHours(1)))
.withCacheConfiguration("exchangeRates", json.entryTtl(Duration.ofMinutes(5)));
}
}
redis-cli> KEYS webnest:cache:* (fine locally; use SCAN in production)
1) "webnest:cache:courseBySlug::spring-boot"
redis-cli> TTL "webnest:cache:courseBySlug::spring-boot"
(integer) 3587
redis-cli> GET "webnest:cache:courseBySlug::spring-boot"
"{\"@class\":\"com.webnest.CourseDto\",\"slug\":\"spring-boot\",\"title\":\"Spring Boot\"}"
Shared HTTP sessions and pub/sub between instances
<!-- Spring Session: store HttpSession in Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-session-data-redis</artifactId>
</dependency>
# application.yml
spring:
session:
timeout: 30m
// Pub/sub: notify all instances when a course is published
@Configuration
public class PubSubConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory cf, CoursePublishedListener listener) {
RedisMessageListenerContainer c = new RedisMessageListenerContainer();
c.setConnectionFactory(cf);
c.addMessageListener(listener, new ChannelTopic("webnest:course-published"));
return c;
}
}
@Component
public class CoursePublishedListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
System.out.println("Course published: " + new String(message.getBody()));
}
}
// publisher
redis.convertAndSend("webnest:course-published", "spring-ai");
Instance A publishes "spring-ai"
Instance A: Course published: spring-ai
Instance B: Course published: spring-ai
(User sessions survive restarts and work on any instance behind the load balancer.)
Common Mistakes
- Using RedisTemplate with default JDK serialization, producing unreadable values that break when classes change.
- Storing cache-like keys without TTLs, slowly filling Redis memory.
- Running KEYS * in production, which blocks Redis for all clients.
- Treating Redis as the only copy of important data without persistence or replication configured.
- Building a home-grown distributed lock without expiry, leaving locks held forever after a crash.
Key Points to Remember
- spring-boot-starter-data-redis auto-configures Lettuce, StringRedisTemplate and RedisTemplate.
- Use opsForValue/Hash/List/Set/ZSet for Redis data structures; prefer string or JSON serialization.
- spring.cache.type=redis turns Redis into a shared cache with configurable TTLs.
- Redis enables rate limiting, leaderboards, shared sessions, locks and pub/sub.
- Always set expiries, key prefixes and memory limits; use SCAN instead of KEYS.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.