What is Caching and Why Does It Matter?
Caching means storing frequently accessed data in fast, temporary storage (RAM) so future requests can be served without querying slow relational databases or calling external third-party APIs.
Offloads 80-95% of repeated read queries from PostgreSQL or MySQL, saving disk I/O and connection pools.
RAM lookups return data in sub-milliseconds (e.g., 0.5ms) compared to 50–200ms database disk queries.
Enables your backend to effortlessly handle flash sales, sudden traffic surges, and high concurrent user loads.
“Spring Boot already provides caching out of the box... but Redis takes caching to the next level.”
However, not all caches work the same way. Spring Boot supports two primary categories: In-Memory Cache and Distributed Cache. Understanding their fundamental trade-offs is essential before going to production.
In-Memory Caching (How It Works)
Spring Boot provides in-memory caching by default when you add the @EnableCaching annotation.
Characteristics of In-Memory Cache:
- •Stored in JVM Heap Memory: The cached entries live directly inside the Java Virtual Machine process heap.
- •Default Implementation: Internally uses a thread-safe
ConcurrentHashMap(or Caffeine/Ehcache if configured). - •Zero Configuration: Simple and blazingly fast to set up for local development.
Spring Cache Annotations Suite:
// Spring Boot Caching Annotations
package com.example.demo.service;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepo;
public UserService(UserRepository userRepo) {
this.userRepo = userRepo;
}
// 1. @Cacheable: If key exists in cache, return it. Otherwise execute DB query and cache result.
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
System.out.println("Fetching from Database for ID: " + id);
return userRepo.findById(id).orElseThrow();
}
// 2. @CachePut: Always executes method and updates cache with fresh data
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepo.save(user);
}
// 3. @CacheEvict: Removes entry from cache on deletion to prevent stale reads
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepo.deleteById(id);
}
}The Fatal Flaw with In-Memory Caching
In-memory caching works fine for a monolithic, single-instance app on a developer's laptop. However, modern production environments deploy applications across multiple container instances (Kubernetes pods, AWS ECS, or autoscaling EC2 groups).
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ Spring Boot Pod 1 │ │ Spring Boot Pod 2 │
│ [Local JVM Cache] │ │ [Local JVM Cache] │
│ user:101 'Shyam' │ │ user:101 'OLD_NAME' │ ← STALE DATA!
└──────────────────────┘ └──────────────────────┘
Redis: The Distributed In-Memory Solution
Redis (Remote Dictionary Server) is an open-source, in-memory NoSQL data store that operates as an external, centralized caching tier shared by all microservice instances.
│ │ │ │
└─────────┬───────┴────────┬────────┴────────┬────────┘
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ CENTRALIZED REDIS CLUSTER │
│ (Shared, High-Availability RAM) │
└────────────────────┬────────────────────┘
│ (Only on Cache Miss)
▼
┌─────────────────────────────────────────┐
│ Primary SQL Database (Disk) │
└─────────────────────────────────────────┘
Why Redis is Production-Ready:
The Magic: You Just Plug Redis Underneath
Here is the best part of Spring's architectural design: All Spring Cache annotations (@Cacheable, @CacheEvict) remain 100% identical!
“Spring gives you the clean abstraction. Redis gives you the distributed power.”
You do NOT need to rewrite your business services withredisTemplate.opsForValue()calls. You simply add the Redis dependency, configure the host in YAML, and Spring Boot automatically binds its cache manager to Redis.
# application.yml
# Switching from default in-memory cache to distributed Redis
spring:
cache:
type: redis
redis:
time-to-live: 600000 # 10 minutes TTL
cache-null-values: false
data:
redis:
host: ${REDIS_HOST:localhost}
port: 6379
timeout: 2000ms
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2In-Memory vs. Redis Comparison
| Criteria | Default In-Memory Cache | Redis Distributed Cache |
|---|---|---|
| Storage Location | Application JVM Heap Memory | External Centralized RAM Node |
| Multiple Instances | Not Shared (Separate cache per node) | Shared across all nodes & services |
| Horizontal Scaling | Fails (Causes data inconsistencies) | Seamless horizontal scaling |
| App Restart Survival | Cache is completely wiped out | Survives deployments & restarts |
| Infrastructure Cost | Zero extra infra needed | Requires managing Redis instance/cluster |
| Best For | Small apps, monolithic, single pod | Microservices, Kubernetes, High-Traffic |
Final Takeaway: Scale Safely
Spring Boot caching is great to start with. Redis is what you need to scale safely:
Did you find this distributed caching breakdown useful?
Help other backend engineers build resilient systems — please like and share with your team!