A demonstration project showcasing 4 different caching implementations with Spring Boot, applied to the same CRUD application (Customer).
| Module | Cache type | Use case | Configuration | Port |
|---|---|---|---|---|
spring-cache-caffeine |
Local in-process cache | Standalone app, high performance | Java (CacheConfig) |
8083 |
spring-cache-redis |
Distributed cache | Microservices, horizontal scaling | Java (RedisConfig) |
8084 |
spring-jcache-ehcache |
Local JCache (JSR-107) | Multi-tier cache (heap + off-heap + disk) | XML (ehcache.xml) |
8081 |
spring-jcache-hazelcast |
Distributed JCache (JSR-107) | Distributed data grid | XML (hazelcast.xml) |
8082 |
- Java 21
- Docker & Docker Compose
docker-compose up -dThis starts:
- PostgreSQL 16 on port
5433 - Redis 7 on port
6379
./gradlew build# Caffeine (local cache)
./gradlew :spring-cache-caffeine:bootRun
# Redis (distributed cache)
./gradlew :spring-cache-redis:bootRun
# Ehcache (local JCache)
./gradlew :spring-jcache-ehcache:bootRun
# Hazelcast (distributed JCache)
./gradlew :spring-jcache-hazelcast:bootRun| Method | URL | Description |
|---|---|---|
POST |
/customers/save-all?number=100 |
Batch insert customers |
POST |
/customers/save-all?number=100&hibernate=true |
Batch insert via Hibernate |
GET |
/customers/get-all |
Get all customers (cached) |
PUT |
/customers/update-all?hibernate=false |
Batch update (evicts cache) |
DELETE |
/customers/delete-all |
Delete all customers |
Each module also exposes Swagger UI at /swagger-ui.html.
# 1. Insert data
curl -X POST "http://localhost:8083/customers/save-all?number=100"
# 2. First call: reads from the database
curl http://localhost:8083/customers/get-all
# 3. Second call: reads from cache (faster)
curl http://localhost:8083/customers/get-all
# 4. Update: evicts the cache
curl -X PUT "http://localhost:8083/customers/update-all?hibernate=false"
# 5. Next call: reads from the database again
curl http://localhost:8083/customers/get-all
# 6. Cleanup
curl -X DELETE http://localhost:8083/customers/delete-allEach module applies the same caching strategy on the service layer via Spring annotations:
@Cacheable(value = "customers", key = "#root.methodName")ongetAllCustomers()— caches the result; subsequent calls skip the database@CacheEvict(value = "customers", allEntries = true)onupdateAllByBatch()— evicts the entire cache so the next read fetches fresh data
The difference between modules lies in the cache provider and its configuration.
| Provider | Caffeine via CaffeineCacheManager |
| Config | Java — CacheConfig.java |
| TTL | 30 s (expireAfterWrite) |
| Eviction | Size-based (maximumSize = 10 000) + time-based |
| Extras | recordStats() for hit/miss metrics |
Pure Spring Cache abstraction, no XML. Ideal for single-JVM applications needing a fast, lightweight cache.
| Provider | Redis via RedisCacheManager |
| Config | Java — RedisConfig.java |
| TTL | 30 s (entryTtl) |
| Serialization | JSON (GenericJackson2JsonRedisSerializer) instead of Java serialization |
| Infra | Requires a running Redis instance (see docker-compose.yml) |
Same Spring Cache annotations as Caffeine, but the cache is external. Values are stored as human-readable JSON in Redis, making debugging easier.
| Provider | Ehcache 3 via JCache (JSR-107) |
| Config | XML — ehcache.xml |
| TTL | 30 s |
| Tiers | Heap (100 entries) → Off-heap (10 MB) → Disk (20 MB, persistent) |
| Listener | CacheEventLogger — logs CREATED, EXPIRED, EVICTED events asynchronously |
Multi-tier storage: hot entries live on the heap, overflow goes to off-heap memory, then to persistent disk. The JSR-107 standard means the same annotations work, but configuration is provider-specific XML.
| Provider | Hazelcast 5 via JCache (JSR-107) |
| Config | XML — hazelcast.xml |
| TTL | 1 min (expiry policy on CREATED) |
| Listener | CacheEntryListener — logs CREATED, UPDATED, REMOVED, EXPIRED events |
| Extras | Statistics enabled, cluster-aware (distributed data grid) |
Hazelcast embeds a distributed data grid in the JVM. Cache entries are automatically replicated across cluster members. The CacheEntryListener implements Factory<CacheEntryListener> as required by the Hazelcast JCache SPI.