Redis Caching
The in-memory cache covered in Caching lives inside one JVM, so each instance has its own copy and nothing survives a restart. Redis solves both problems: it is a fast, networked key-value store that all your application instances share, giving you a single, consistent, persistent cache. Best of all, the @Cacheable / @CacheEvict / @CachePut annotations don’t change — only the backing CacheManager does.
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
The Redis starter brings in Lettuce, the default non-blocking client. When spring-boot-starter-cache and Redis are both present, Spring Boot auto-configures a RedisCacheManager instead of the in-memory one.
Running Redis locally
Spin up Redis with Docker for development:
docker run --name redis -p 6379:6379 -d redis:7
Tip: Verify connectivity with
docker exec -it redis redis-cli ping— it should replyPONG.
Configuration
Point Spring at Redis and select it as the cache type.
spring:
data:
redis:
host: localhost
port: 6379
# password: ${REDIS_PASSWORD}
timeout: 2s
cache:
type: redis
redis:
time-to-live: 10m # default TTL for all caches
cache-null-values: false # don't store nulls
key-prefix: "app::"
use-key-prefix: true
Enable caching exactly as before:
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
}
@Cacheable("products")
public Product findById(Long id) {
return repository.findById(id).orElseThrow();
}
The first call stores the serialized Product in Redis under a key like app::products::42; every instance now reads the same cached value.
docker exec -it redis redis-cli keys '*'
Output:
1) "app::products::42"
2) "app::products::99"
Per-cache TTL and serialization
Different caches need different lifetimes — a product catalog can live for an hour, a session token for minutes. Customize per cache with a RedisCacheManagerBuilderCustomizer, and switch values to JSON so they are human-readable in Redis (the default uses JDK serialization, which requires Serializable and produces opaque bytes).
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
import java.time.Duration;
@Configuration
public class RedisCacheConfig {
@Bean
RedisCacheConfiguration defaultCacheConfig(ObjectMapper mapper) {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer(mapper)));
}
@Bean
RedisCacheManagerBuilderCustomizer cacheCustomizer() {
return builder -> builder
.withCacheConfiguration("products",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)))
.withCacheConfiguration("sessions",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(15)));
}
}
| Cache | TTL | Backing config |
|---|---|---|
products | 1 hour | per-cache override |
sessions | 15 minutes | per-cache override |
| anything else | 10 minutes | default config |
A JSON-serialized value stored under app::products::42 looks like:
docker exec -it redis redis-cli get 'app::products::42'
Output:
{"@class":"com.example.Product","id":42,"name":"Mechanical Keyboard","price":89.99}
Note:
GenericJackson2JsonRedisSerializerwrites the@classfield so it can deserialize back to the right type. If you cache different types in one cache or evolve your DTOs, keep that type info — or use a typed serializer per cache.
Connecting to a real cluster
In production point at managed Redis (ElastiCache, Memorystore) and externalize credentials:
spring:
data:
redis:
host: ${REDIS_HOST}
port: 6379
ssl:
enabled: true
password: ${REDIS_PASSWORD}
lettuce:
pool:
max-active: 16
max-idle: 8
Warning: A network round-trip is far slower than an in-process map. Cache values that are expensive enough to justify the network cost, and keep them reasonably small — large objects serialized to JSON on every read can become the new bottleneck.
Best Practices
- Reuse the standard cache annotations; only the
CacheManagerchanges between providers. - Set a sensible default TTL and override per cache for differing freshness needs.
- Prefer JSON serialization for debuggability; keep
@classtype info when caching polymorphic values. - Externalize host, password, and TLS through profiles / environment variables.
- Monitor Redis hit/miss and latency via Actuator metrics.