EHCache

JavaLibraryCachingTerracottaEnterpriseDistributed

GitHub Overview

ehcache/ehcache3

Ehcache 3.x line

Stars2,070
Watchers154
Forks584
Created:February 26, 2014
Language:Java
License:Apache License 2.0

Topics

None

Star History

ehcache/ehcache3 Star History
Data as of: 10/22/2025, 10:04 AM

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

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();