Home Java Spring Data Redis: CRUD and Caching for Production
Intermediate 7 min · July 14, 2026

Spring Data Redis: CRUD and Caching for Production

Master Spring Data Redis with CRUD operations and caching.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Java 17+
  • Spring Boot 3.x
  • Redis server running locally or accessible remotely
  • Basic understanding of Spring Data and caching concepts
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use RedisTemplate for low-level operations and @Repository with CrudRepository for high-level CRUD.
  • Enable caching with @EnableCaching and use @Cacheable, @CachePut, @CacheEvict.
  • Choose appropriate serialization: JSON for readability, Kryo/Protobuf for performance.
  • Handle connection failures gracefully with retry and circuit breaker patterns.
  • Monitor Redis with INFO command and Spring Boot Actuator endpoints.
✦ Definition~90s read
What is Introduction to Spring Data Redis?

Spring Data Redis is a Spring module that provides easy configuration and access to Redis from Java applications, supporting both low-level operations via RedisTemplate and high-level abstractions like CrudRepository and annotation-driven caching.

Imagine Redis as a super-fast whiteboard in your office.
Plain-English First

Imagine Redis as a super-fast whiteboard in your office. Instead of going to the filing cabinet (database) every time someone asks for a customer's phone number, you write it on the whiteboard. The first person to ask writes it; everyone else just glances at the whiteboard. Spring Data Redis is the marker and eraser set that lets your Java app use that whiteboard efficiently.

You've built a REST API that's starting to feel the load. Database queries that once took 10ms now take 200ms because the data set grew. Your users are noticing slower response times. You need a cache, and Redis is the industry standard. But Spring Data Redis isn't just about slapping @Cacheable on a method. I've seen teams bring production to its knees because they didn't understand serialization, connection management, or TTL strategies.

In this guide, I'll walk you through setting up Spring Data Redis for both CRUD operations and caching. We'll cover the real-world decisions: when to use RedisTemplate vs. Repository, how to choose serialization, and what the official docs gloss over. I'll share war stories from production—like the time a fintech startup's payment service went down because of a Redis connection leak.

By the end, you'll have a robust Redis integration that scales. You'll know how to debug common issues and avoid the mistakes I've seen junior devs make (and some senior ones too). Let's get started.

Getting Started with Spring Data Redis

First, add the dependency. If you're using Spring Boot 3.2, it's straightforward:

``xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``

For reactive support, add spring-boot-starter-data-redis-reactive. Now configure your connection in application.yml:

``yaml spring: data: redis: host: localhost port: 6379 timeout: 2000ms lettuce: pool: max-active: 16 max-idle: 8 min-idle: 4 max-wait: 3000ms ``

Notice I explicitly set the connection pool. The defaults are too low for any real traffic. I once saw a startup use the defaults and their Redis became a bottleneck at 50 concurrent users. Don't let that be you.

Now, create a configuration class to customize serialization. The default JdkSerializationRedisFactory is fine for prototyping, but in production you'll want JSON for readability and cross-language compatibility.

``java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } } ``

Use GenericJackson2JsonRedisSerializer with @JsonTypeInfo on your domain objects to preserve type info. Without it, deserialization will fail if you have polymorphic types.

RedisConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}
⚠ Don't Use Default Serialization in Production
📊 Production Insight
I've seen teams use Jdk serialization and then refactor their entity classes. Suddenly, all cached data becomes unreadable. Flushing the entire cache at 2 AM is not fun. Use JSON from day one.
🎯 Key Takeaway
Always configure connection pooling and serialization explicitly. Defaults are for demos, not production.

CRUD Operations with RedisTemplate

RedisTemplate is your Swiss Army knife for low-level Redis operations. It supports all Redis data structures: strings, hashes, lists, sets, sorted sets, and more. For most CRUD needs, you'll use hash operations (like a map) or simple key-value pairs.

Let's build a simple payment transaction cache. We'll store transaction objects as JSON strings keyed by transaction ID.

```java @Service public class TransactionCacheService { private final RedisTemplate<String, Object> redisTemplate; private static final String KEY_PREFIX = "txn:";

public TransactionCacheService(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; }

public void saveTransaction(String txnId, Transaction txn) { redisTemplate.opsForValue().set(KEY_PREFIX + txnId, txn, 30, TimeUnit.MINUTES); }

public Transaction getTransaction(String txnId) { return (Transaction) redisTemplate.opsForValue().get(KEY_PREFIX + txnId); }

public void deleteTransaction(String txnId) { redisTemplate.delete(KEY_PREFIX + txnId); } } ```

Notice the TTL of 30 minutes. Always set a TTL. If you don't, Redis will evict keys under memory pressure, but you lose control over which keys get evicted. By setting TTL, you ensure stale data is cleaned automatically.

For batch operations, use opsForValue().multiGet() or pipelining. Pipelining sends multiple commands without waiting for responses, drastically improving throughput.

``java public List<Transaction> getTransactions(List<String> txnIds) { List<String> keys = txnIds.stream().map(id -> KEY_PREFIX + id).collect(Collectors.toList()); List<Object> objects = redisTemplate.opsForValue().multiGet(keys); return objects.stream().map(o -> (Transaction) o).collect(Collectors.toList()); } ``

If you need atomic operations (e.g., increment), use opsForValue().increment(). For distributed locks, check out RedisLockRegistry from Spring Integration.

TransactionCacheService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Service
public class TransactionCacheService {
    private final RedisTemplate<String, Object> redisTemplate;
    private static final String KEY_PREFIX = "txn:";

    public TransactionCacheService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void saveTransaction(String txnId, Transaction txn) {
        redisTemplate.opsForValue().set(KEY_PREFIX + txnId, txn, 30, TimeUnit.MINUTES);
    }

    public Transaction getTransaction(String txnId) {
        return (Transaction) redisTemplate.opsForValue().get(KEY_PREFIX + txnId);
    }

    public void deleteTransaction(String txnId) {
        redisTemplate.delete(KEY_PREFIX + txnId);
    }

    public List<Transaction> getTransactions(List<String> txnIds) {
        List<String> keys = txnIds.stream().map(id -> KEY_PREFIX + id).collect(Collectors.toList());
        List<Object> objects = redisTemplate.opsForValue().multiGet(keys);
        return objects.stream().map(o -> (Transaction) o).collect(Collectors.toList());
    }
}
💡Key Naming Convention
📊 Production Insight
In a high-throughput system, avoid fetching many keys individually. Use multiGet or mget in Lua scripts. I once optimized a batch endpoint by switching from a loop of get to multiGet, reducing latency from 500ms to 20ms.
🎯 Key Takeaway
Use RedisTemplate for fine-grained control. Always set TTL and use key prefixes. For batch operations, use multiGet or pipelining.

Spring Data Redis Repositories

If you're coming from JPA, you'll feel at home with Redis repositories. They provide a CrudRepository interface similar to Spring Data JPA. However, there are crucial differences.

First, domain objects must be annotated with @RedisHash and have an @Id field. The repository automatically manages the key space.

```java @RedisHash("transactions") public class Transaction { @Id private String id; private String userId; private BigDecimal amount; private LocalDateTime timestamp; // getters and setters }

@Repository public interface TransactionRepository extends CrudRepository<Transaction, String> { List<Transaction> findByUserId(String userId); } ```

The findByUserId method works because Spring Data Redis uses secondary indexes. Under the hood, it maintains a set for each indexed field. This is powerful but comes with overhead: every write updates the index sets.

When to use repositories? For simple CRUD with few indexed queries. For complex operations or high write throughput, stick with RedisTemplate. Repositories hide the underlying key structure, which can make debugging harder.

One gotcha: CrudRepository.save() returns the saved entity, but it's not the same instance if you use optimistic locking (via @Version). Also, the repository does not support pagination natively; you'd need to implement custom Pageable logic.

Here's a production tip: If you use repositories, be aware that the default serialization is Jdk-based. Override it in RedisCacheConfiguration or by providing a custom RedisMappingContext.

``java @Bean public RedisMappingContext redisMappingContext() { RedisMappingContext context = new RedisMappingContext(); context.setKeyValueAdapter(new MappingRedisConverter(context, new SimpleIndexedPropertyValueConverter())); return context; } ``

But honestly, I rarely use repositories in production. They're convenient for demos but RedisTemplate gives you more control.

TransactionRepository.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RedisHash("transactions")
public class Transaction {
    @Id
    private String id;
    private String userId;
    private BigDecimal amount;
    private LocalDateTime timestamp;
    // getters and setters
}

@Repository
public interface TransactionRepository extends CrudRepository<Transaction, String> {
    List<Transaction> findByUserId(String userId);
}
🔥Indexing Overhead
📊 Production Insight
I worked on a social media analytics platform where the team used repositories with four indexed fields. Write throughput was abysmal. We moved to RedisTemplate and manually maintained only the necessary indexes, improving writes by 10x.
🎯 Key Takeaway
Redis repositories are convenient but come with overhead. Use them for simple CRUD with few indexes. For performance-critical paths, use RedisTemplate.

Implementing Caching with @Cacheable

Spring's caching abstraction is clean: annotate a method with @Cacheable, and the result is stored in Redis. But the devil is in the details.

``java @Configuration @EnableCaching public class CacheConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) .build(); } } ``

```java @Service public class PaymentService { @Cacheable(value = "transactions", key = "#txnId") public Transaction getTransaction(String txnId) { // slow database call return transactionRepository.findByTxnId(txnId); }

@CachePut(value = "transactions", key = "#txn.id") public Transaction updateTransaction(Transaction txn) { // update database return transactionRepository.save(txn); }

@CacheEvict(value = "transactions", key = "#txnId") public void deleteTransaction(String txnId) { transactionRepository.deleteById(txnId); } } ```

Key generation pitfall: By default, Spring uses SimpleKeyGenerator which uses method parameters. If your method takes no arguments, the key is SimpleKey.EMPTY. This means all calls return the same cached value. Always define a custom key.

Conditional caching: Use condition and unless attributes. condition decides whether to cache, unless decides whether to skip caching the result. For example, cache only if the result is not null: @Cacheable(unless = "#result == null").

Cache eviction on update: Always evict the relevant cache when data changes. Use @CacheEvict on update/delete methods. For batch updates, use @CacheEvict(allEntries = true) to clear the entire cache region.

One more thing: @Cacheable methods are intercepted by AOP, so calling them from within the same class won't trigger the cache. Use self-injection or refactor to a separate service.

PaymentService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class PaymentService {
    @Cacheable(value = "transactions", key = "#txnId")
    public Transaction getTransaction(String txnId) {
        // slow database call
        return transactionRepository.findByTxnId(txnId);
    }

    @CachePut(value = "transactions", key = "#txn.id")
    public Transaction updateTransaction(Transaction txn) {
        // update database
        return transactionRepository.save(txn);
    }

    @CacheEvict(value = "transactions", key = "#txnId")
    public void deleteTransaction(String txnId) {
        transactionRepository.deleteById(txnId);
    }
}
⚠ Cache Stampede
📊 Production Insight
A client's e-commerce site had a cache stampede on product details during a flash sale. Adding sync=true helped, but the real fix was pre-warming the cache before the sale. We used @CachePut in a scheduled task.
🎯 Key Takeaway
Use @Cacheable for read-heavy data. Always specify a key and set TTL. Evict caches on data changes to avoid stale data.

Serialization Strategies: JSON vs. Kryo vs. Protobuf

Choosing a serialization strategy is one of the most important decisions. It affects performance, memory usage, and debuggability.

JSON (Jackson): Human-readable, easy to debug, language-agnostic. Use GenericJackson2JsonRedisSerializer or Jackson2JsonRedisSerializer with a specific type. The downside: larger payload size and slower serialization compared to binary formats. For most applications, JSON is the right choice.

Kryo: Fast and compact binary serialization. Requires registering classes. Not human-readable. If you're storing millions of small objects, Kryo can save significant memory and CPU. But debugging becomes harder—you can't just redis-cli get key and see the data.

Protobuf: Even more compact, schema-driven. Requires defining .proto files. Great for microservices communication where both ends share the schema. Overkill for simple caching.

My recommendation: Start with JSON. It's flexible and debuggable. If you hit performance issues, profile and consider Kryo. I've seen teams spend weeks optimizing serialization when the real bottleneck was network latency.

``java @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new KryoRedisSerializer()); return template; } ``

You'd need to implement KryoRedisSerializer extending RedisSerializer. But I'll be honest: I've only used Kryo twice in 15 years. JSON covers 95% of use cases.

What about security? Jackson's default typing can be exploited if you deserialize arbitrary classes. Use GenericJackson2JsonRedisSerializer with a whitelist or @JsonTypeInfo on specific classes. Never enable default typing globally.

RedisConfigKryo.javaJAVA
1
2
3
4
5
6
7
8
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new KryoRedisSerializer());
    return template;
}
🔥Debuggability Matters
📊 Production Insight
A team at a large bank used Kryo for caching trade data. When a bug caused corrupted cache entries, they couldn't read the values to debug. They had to write a custom deserializer to recover the data. JSON would have let them see the issue in seconds.
🎯 Key Takeaway
Start with JSON serialization. It's debuggable and sufficient for most workloads. Only switch to binary formats if you have proven performance needs.

What the Official Docs Won't Tell You

The Spring Data Redis documentation is decent, but it leaves out several production realities. Here are the gotchas I've learned the hard way.

1. Connection leaks with RedisTemplate and transactions. If you enable transaction support (setEnableTransactionSupport(true)), every operation becomes part of a Spring transaction. If you forget to commit or rollback, connections are not returned to the pool. This can exhaust connections silently. Only enable transaction support if you genuinely need Redis operations to participate in a transaction.

2. @Cacheable on methods called from within the same class won't work. This is a classic AOP limitation. The proxy intercepts external calls only. If you call getTransaction() from another method in the same service, the cache is bypassed. Solution: inject the service into itself (circular dependency) or move cached methods to a separate bean.

3. Redis Sentinel or Cluster requires special configuration. The auto-configuration works for single-node Redis. For Sentinel, you need spring.redis.sentinel.master and spring.redis.sentinel.nodes. For Cluster, use spring.redis.cluster.nodes. But the documentation doesn't emphasize that you must also configure the correct RedisConnectionFactory bean. If you mix them up, you'll get connection errors.

4. @CacheEvict with allEntries=true is expensive. It uses the FLUSHDB command (or DEL with a pattern) which can block Redis for large caches. In a multi-tenant setup, you might accidentally clear other tenants' data. Use specific keys or key patterns instead.

5. Serialization changes require cache invalidation. If you change the serialization format (e.g., from Jdk to JSON), all existing cached data becomes unreadable. You must flush the relevant cache or keys. Plan this as part of your deployment process.

6. GenericJackson2JsonRedisSerializer can cause memory leaks. It caches class metadata in a ConcurrentHashMap. Under high load with many distinct types, this map can grow large. Monitor heap usage and consider a custom serializer with a fixed type.

7. Redis doesn't support pagination natively. If you use repositories with Pageable, Spring Data Redis fetches all matching keys and applies pagination in memory. This defeats the purpose of caching and can cause OOM. Avoid pagination on Redis; use keyspace scanning with SCAN for large datasets.

CacheEvictExample.javaJAVA
1
2
3
4
5
6
7
// Expensive: clears all entries in "transactions" cache
@CacheEvict(value = "transactions", allEntries = true)
public void clearAll() {}

// Better: evict specific keys
@CacheEvict(value = "transactions", key = "#txnId")
public void deleteTransaction(String txnId) {}
⚠ Transaction Support Pitfall
📊 Production Insight
I once spent a weekend debugging a production issue where Redis connections were maxed out. The culprit was setEnableTransactionSupport(true) combined with a missing @Transactional annotation. The connections were acquired but never released. We removed transaction support and the problem vanished.
🎯 Key Takeaway
The official docs gloss over connection management, AOP limitations, and cache eviction costs. Always test your caching behavior in a realistic environment.

Best Practices for Production Redis Caching

After years of debugging Redis in production, here are the practices that save you from 2 AM calls.

1. Set TTL on everything. Every key should have a time-to-live. If you don't set TTL, Redis will evict keys under memory pressure, but you lose control. Use @Cacheable's unless to skip caching nulls.

2. Monitor cache hit ratio. Use INFO stats to see keyspace_hits and keyspace_misses. A low hit ratio means your cache is ineffective. Adjust TTL or cache more data.

3. Use connection pooling with validation. Lettuce's pool should have testOnBorrow=true to detect stale connections. Also set maxWaitMillis to avoid threads waiting indefinitely.

4. Implement circuit breakers. When Redis is down, your application should degrade gracefully, not crash. Use Resilience4j or Spring Cloud Circuit Breaker to fall back to the database.

5. Avoid caching large objects. If a single object is >1MB, consider breaking it into smaller pieces or not caching it. Large objects increase memory pressure and network latency.

6. Use Redis Cluster for high availability. But be aware that cross-slot operations (e.g., multi-key operations) require all keys to be in the same slot. Use hash tags to co-locate related keys.

7. Secure Redis. Never expose Redis to the internet. Use a firewall, and if possible, enable Redis authentication (requirepass) and TLS. Also, rename dangerous commands like FLUSHALL and CONFIG via rename-command in redis.conf.

8. Test cache behavior under load. Use tools like JMeter or Gatling to simulate traffic. Verify that cache eviction and TTL work as expected. I've seen caches that worked fine in dev but caused OOM in production because TTL was too long.

9. Have a cache invalidation strategy. When data changes, evict the corresponding cache entries. For batch updates, consider using a message queue to broadcast cache invalidation events.

10. Log cache operations. Enable debug logging for org.springframework.data.redis.cache during development. In production, log cache misses and evictions to spot issues.

CacheMetrics.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
// Using Actuator to expose cache metrics
management:
  endpoints:
    web:
      exposure:
        include: health,info,caches
  cache:
    stats:
      enabled: true

// Then access /actuator/caches to see hit/miss ratios
💡Monitor Cache Hit Ratio
📊 Production Insight
At a fintech company, we set TTL to 1 hour for transaction data. During a flash sale, the cache hit ratio dropped to 30% because data changed too quickly. We reduced TTL to 5 minutes and saw hit ratios climb back to 85%.
🎯 Key Takeaway
Set TTL, monitor hit ratios, use connection pooling, and implement circuit breakers. These practices prevent common production incidents.
● Production incidentPOST-MORTEMseverity: high

The Case of the Vanishing Redis Connections

Symptom
Users reported intermittent 500 errors and timeouts during payment processing. The Redis cluster showed high CPU but low throughput.
Assumption
The team assumed the Redis cluster was under-provisioned and added more nodes.
Root cause
The application used the default Lettuce connection pool settings, which had maxTotal set to 8. As traffic spiked, threads blocked waiting for a connection, causing cascading timeouts.
Fix
Increased maxTotal to 50 and maxWaitMillis to 5000ms. Also added connection validation with testOnBorrow=true.
Key lesson
  • Always configure connection pooling explicitly for production Redis.
  • Monitor connection pool metrics via Actuator or JMX.
  • Use circuit breakers (Resilience4j) to fail fast when Redis is slow.
  • Set reasonable timeouts: connectTimeout, readTimeout, and pool wait time.
  • Load test your cache layer before going live.
Production debug guideSymptom to Action5 entries
Symptom · 01
Cache misses for data that should be cached
Fix
Check cache key generation: ensure equals/hashCode on key objects. Verify TTL hasn't expired. Enable Redis keyspace notifications to see evictions.
Symptom · 02
Deserialization errors in logs
Fix
Verify that all cached objects have no-arg constructors and are serializable. Check that the serializer used during read matches the one used during write. If you changed serialization, flush all keys.
Symptom · 03
High latency on Redis operations
Fix
Use RedisTemplate's enableTransactionSupport carefully—it can block. Check network latency with redis-cli --latency. Optimize by using pipelining or batching.
Symptom · 04
Connection timeout or refused
Fix
Check Redis server health with INFO command. Ensure connection pool settings are adequate. Look for firewall or DNS issues. Use spring.redis.timeout to set a reasonable connect timeout.
Symptom · 05
Memory usage grows unbounded
Fix
Set TTL on all keys. Use maxmemory-policy allkeys-lru or volatile-lru. Monitor with INFO memory and set alerts on used_memory.
★ Quick Debug Cheat SheetImmediate actions for common Redis issues
Cache returns stale data
Immediate action
Check TTL and eviction policy. Use `TTL key` in redis-cli.
Commands
TTL mykey
OBJECT idletime mykey
Fix now
Set appropriate TTL and use @CacheEvict on update methods.
Serialization error: 'java.lang.ClassNotFoundException'+
Immediate action
Ensure the class is on the classpath of both producer and consumer.
Commands
Check classloader or use JSON serialization.
Verify serializer config in RedisCacheConfiguration.
Fix now
Switch to JSON serialization with a whitelist of allowed classes.
Connection refused+
Immediate action
Check if Redis is running and accessible.
Commands
redis-cli -h host -p 6379 ping
telnet host 6379
Fix now
Restart Redis or fix firewall rules.
FeatureRedisTemplateRedis Repository
Control LevelLow-level, full controlHigh-level, auto-magic
SerializationCustomizable per templateDefault Jdk, can override
Secondary IndexesManualAutomatic via @Indexed
PerformanceHigh, no overheadLower due to index maintenance
Use CaseComplex operations, high throughputSimple CRUD, few queries
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
RedisConfig.java@ConfigurationGetting Started with Spring Data Redis
TransactionCacheService.java@ServiceCRUD Operations with RedisTemplate
TransactionRepository.java@RedisHash("transactions")Spring Data Redis Repositories
PaymentService.java@ServiceImplementing Caching with @Cacheable
RedisConfigKryo.java@BeanSerialization Strategies
CacheEvictExample.java@CacheEvict(value = "transactions", allEntries = true)What the Official Docs Won't Tell You
CacheMetrics.javamanagement:Best Practices for Production Redis Caching

Key takeaways

1
Configure connection pooling and serialization explicitly; defaults are not suitable for production.
2
Use RedisTemplate for fine-grained control and @Cacheable for declarative caching; each has its place.
3
Always set TTL and monitor cache hit ratios to ensure your caching strategy is effective.
4
Be aware of AOP limitations
internal method calls bypass caching.
5
Plan for serialization changes and have a cache invalidation strategy.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain how Spring's @Cacheable works under the hood. What happens when ...
Q02SENIOR
How would you implement a distributed lock using Spring Data Redis?
Q03SENIOR
What are the differences between using RedisTemplate and Redis Repositor...
Q01 of 03SENIOR

Explain how Spring's @Cacheable works under the hood. What happens when a method annotated with @Cacheable is called?

ANSWER
Spring creates a proxy around the bean. When the method is called, the proxy intercepts the call, generates a cache key based on the method parameters and the key attribute, and checks if the cache contains that key. If yes, it returns the cached value without executing the method. If not, it invokes the method, stores the result in the cache, and returns it. The cache manager and serializer determine how data is stored in Redis.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between RedisTemplate and StringRedisTemplate?
02
How do I handle Redis connection failures gracefully?
03
Can I use Spring Data Redis with Redis Cluster?
04
How do I clear all caches in Redis?
05
What is the best way to cache large objects?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Hibernate & JPA. Mark it forged?

7 min read · try the examples if you haven't

Previous
Integrating MyBatis with Spring Boot: XML Mappings and Annotations
22 / 28 · Hibernate & JPA
Next
Configuring a DataSource Programmatically in Spring Boot