EHCache
GitHub Overview
ehcache/ehcache3
Ehcache 3.x line
Topics
Star History
Library
EHCache
Overview
EHCache is an open source, standards-based cache library for Java applications that boosts performance, offloads your database, and simplifies scalability. It's the most widely-used Java-based cache because it's robust, proven, and full-featured.
Details
EHCache 3 is a robust, proven, full-featured caching library that integrates easily with other popular libraries and frameworks. Its modern API design provides type-safe APIs and configuration, delivering a greatly improved coding experience compared to EHCache 2.x. It natively complies with JSR-107 (JCache spec), supports off-heap storage, and offers many other additions and improvements. The tiering model allows storing increasing amounts of data on slower tiers (which are generally more abundant) while faster storage resources are more rare but located where the 'hottest' data is preferred. It provides three storage options: On-Heap, Off-Heap, and Clustered, delivering excellent performance in highly concurrent environments. By adding Terracotta, EHCache can scale to any use case, enabling clustered caches over a distributed deployment architecture. EHCache 3.0 is specifically built and tested to run well under highly concurrent access on systems with dozens of CPU cores. The largest EHCache installations utilize multiple terabytes of data storage, and with off-heap storage, EHCache has been tested to store 6TB data in a single process (JVM).
Pros and Cons
Pros
- Excellent Performance: Direct get(key) operations under 500ns with exceptional speed
- Enterprise Features: Comprehensive solution including monitoring, management, and security features
- Scalability: Supports terabyte-scale data storage with Terracotta
- JSR-107 Compliance: Provides the most complete implementation of the JCache specification
- Flexible Storage: Multi-tier storage with On-Heap, Off-Heap, and Clustered options
- Spring Integration: Spring Boot auto-configuration and annotation support
- High Concurrency: Excellent concurrent access performance in multi-core environments
Cons
- Configuration Complexity: Advanced features can lead to complex configuration
- Memory Usage: Large-scale caches require substantial memory resources
- Terracotta Dependency: Clustering features require Terracotta server
- Learning Curve: Rich features result in high initial learning cost
- Version Compatibility: Migration from EHCache 2.x to 3.x requires API changes
- Debug Complexity: Debugging can be challenging in distributed environments
Key Links
- EHCache Official Site
- EHCache 3 Documentation
- EHCache GitHub Repository
- EHCache Features Overview
- Spring Boot EHCache Integration
- Terracotta BigMemory
Code Examples
Basic Configuration and Usage
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
// Create cache manager
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("myCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class, // Key, Value types
ResourcePoolsBuilder.heap(100)) // 100 entries heap
)
.build();
cacheManager.init();
// Get and use cache
Cache<String, String> myCache = cacheManager.getCache("myCache",
String.class, String.class);
// Store and retrieve data
myCache.put("key1", "value1");
String value = myCache.get("key1");
System.out.println("Retrieved: " + value);
// Check cache
if (myCache.containsKey("key1")) {
System.out.println("Key exists in cache");
}
// Close cache manager
cacheManager.close();
Multi-Tier Storage Configuration
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.config.builders.ResourcePoolsBuilder;
// Three-tier storage: heap, off-heap, disk
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence("/data/ehcache"))
.withCache("tieredCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1000) // 1000 entries heap
.offheap(10, MemoryUnit.MB) // 10MB off-heap
.disk(100, MemoryUnit.MB, true) // 100MB persistent disk
)
)
.build();
cacheManager.init();
Cache<String, String> tieredCache = cacheManager.getCache("tieredCache",
String.class, String.class);
// Large data storage test
for (int i = 0; i < 5000; i++) {
tieredCache.put("key" + i, "Large data value " + i);
}
// Data retrieval performance test
long startTime = System.nanoTime();
String result = tieredCache.get("key2500");
long endTime = System.nanoTime();
System.out.printf("Get operation took: %d ns%n", endTime - startTime);
cacheManager.close();
Spring Boot Integration
// Spring Boot configuration class
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return CacheManagerBuilder.newCacheManagerBuilder()
.withCache("userCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
Long.class, User.class,
ResourcePoolsBuilder.heap(1000)
.offheap(5, MemoryUnit.MB)
)
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(
Duration.ofMinutes(30)))
)
.withCache("productCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, Product.class,
ResourcePoolsBuilder.heap(500)
.offheap(2, MemoryUnit.MB)
)
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(
Duration.ofMinutes(15)))
)
.build(true); // Auto initialization
}
}
// Cache usage in service class
@Service
public class UserService {
@Cacheable(value = "userCache", key = "#userId")
public User getUserById(Long userId) {
// Fetch user from database (heavy operation)
System.out.println("Fetching user from database: " + userId);
return userRepository.findById(userId)
.orElse(null);
}
@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
// Update user and cache
User updatedUser = userRepository.save(user);
System.out.println("User updated and cached: " + user.getId());
return updatedUser;
}
@CacheEvict(value = "userCache", key = "#userId")
public void deleteUser(Long userId) {
// Delete user and clear cache
userRepository.deleteById(userId);
System.out.println("User deleted and cache evicted: " + userId);
}
@CacheEvict(value = "userCache", allEntries = true)
public void clearAllUsers() {
// Clear all user cache
System.out.println("All user cache cleared");
}
}
Cluster Configuration (Using Terracotta)
import org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder;
import org.ehcache.clustered.client.config.builders.ClusteringServiceConfigurationBuilder;
import org.ehcache.config.units.MemoryUnit;
// Terracotta cluster configuration
CacheManager clusteredCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(ClusteringServiceConfigurationBuilder.cluster(
URI.create("terracotta://server1:9410,server2:9410/my-server-entity"))
.autoCreate(server -> server
.defaultServerResource("primary-server-resource")
.resourcePool("resource-pool-a", 10, MemoryUnit.MB)
.resourcePool("resource-pool-b", 20, MemoryUnit.MB)
)
)
.withCache("clusteredCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1000)
.offheap(5, MemoryUnit.MB)
.with(ClusteredResourcePoolBuilder.clusteredDedicated(
"primary-server-resource", 8, MemoryUnit.MB))
)
)
.withCache("sharedCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(500)
.with(ClusteredResourcePoolBuilder.clusteredShared(
"resource-pool-a"))
)
)
.build(true);
// Using cluster cache
Cache<String, String> clusteredCache = clusteredCacheManager
.getCache("clusteredCache", String.class, String.class);
// Share data across multiple nodes
clusteredCache.put("shared-key", "This data is shared across cluster");
// Accessible from other nodes
String sharedValue = clusteredCache.get("shared-key");
System.out.println("Cluster shared value: " + sharedValue);
clusteredCacheManager.close();
Custom Event Listeners
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.ehcache.event.EventType;
// Custom event listener
public class MyCacheEventListener implements CacheEventListener<String, String> {
@Override
public void onEvent(CacheEvent<String, String> event) {
System.out.printf("Cache Event: %s - Key: %s, Old Value: %s, New Value: %s%n",
event.getType(),
event.getKey(),
event.getOldValue(),
event.getNewValue()
);
// Processing for specific events
switch (event.getType()) {
case CREATED:
onCacheEntryCreated(event);
break;
case UPDATED:
onCacheEntryUpdated(event);
break;
case EXPIRED:
onCacheEntryExpired(event);
break;
case EVICTED:
onCacheEntryEvicted(event);
break;
}
}
private void onCacheEntryCreated(CacheEvent<String, String> event) {
// Processing on creation
System.out.println("New cache entry created: " + event.getKey());
}
private void onCacheEntryUpdated(CacheEvent<String, String> event) {
// Processing on update
System.out.println("Cache entry updated: " + event.getKey());
}
private void onCacheEntryExpired(CacheEvent<String, String> event) {
// Processing on expiration
System.out.println("Cache entry expired: " + event.getKey());
}
private void onCacheEntryEvicted(CacheEvent<String, String> event) {
// Processing on eviction
System.out.println("Cache entry evicted: " + event.getKey());
}
}
// Cache configuration with event listener
CacheManager eventCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("eventCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class,
ResourcePoolsBuilder.heap(100)
)
.withService(CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(new MyCacheEventListener(),
EventType.CREATED, EventType.UPDATED,
EventType.EXPIRED, EventType.EVICTED)
.unordered() // No order guarantee (performance priority)
.asynchronous() // Asynchronous execution
)
)
.build(true);
Cache<String, String> eventCache = eventCacheManager
.getCache("eventCache", String.class, String.class);
// Test event generation
eventCache.put("test-key", "test-value"); // CREATED event
eventCache.put("test-key", "updated-value"); // UPDATED event
eventCache.remove("test-key"); // REMOVED event
eventCacheManager.close();
Performance Measurement and Monitoring
import org.ehcache.config.builders.CacheEventListenerConfigurationBuilder;
import org.ehcache.management.registry.DefaultManagementRegistryConfiguration;
import org.ehcache.management.registry.DefaultManagementRegistryService;
// Management and monitoring configuration
CacheManager monitoringCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.using(new DefaultManagementRegistryService(
new DefaultManagementRegistryConfiguration()
.setCacheManagerAlias("myCacheManager")
))
.withCache("performanceCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class,
ResourcePoolsBuilder.heap(1000)
.offheap(10, MemoryUnit.MB)
)
.withService(StatisticsServiceConfigurationBuilder
.newStatisticsServiceConfigurationBuilder()
.build())
)
.build(true);
Cache<String, String> perfCache = monitoringCacheManager
.getCache("performanceCache", String.class, String.class);
// Performance test
long operations = 10000;
long startTime = System.currentTimeMillis();
// Write test
for (int i = 0; i < operations; i++) {
perfCache.put("key" + i, "value" + i);
}
long writeTime = System.currentTimeMillis() - startTime;
System.out.printf("Write %d entries took: %d ms%n", operations, writeTime);
// Read test
startTime = System.currentTimeMillis();
for (int i = 0; i < operations; i++) {
perfCache.get("key" + i);
}
long readTime = System.currentTimeMillis() - startTime;
System.out.printf("Read %d entries took: %d ms%n", operations, readTime);
// Get statistics
CacheStatistics stats = perfCache.getStatistics();
System.out.printf("Cache Statistics:%n");
System.out.printf(" Hit ratio: %.2f%%%n", stats.getCacheHitPercentage());
System.out.printf(" Hit count: %d%n", stats.getCacheHits());
System.out.printf(" Miss count: %d%n", stats.getCacheMisses());
System.out.printf(" Eviction count: %d%n", stats.getCacheEvictions());
monitoringCacheManager.close();