Table of contents
Open Table of contents
Basic Ehcache Usage
Background
Long before Redis, when a Java EE (Java Enterprise Edition) application needed to cache hot data—data accessed frequently but modified rarely—developers often used JDK collections directly. The JVM has limited memory, however, and with large amounts of hot data, developers had to implement expiry and eviction policies themselves. Ehcache primarily addresses these problems. It is particularly suitable for a single-instance application: adding a small JAR avoids the extra system complexity of introducing Redis. Ehcache also provides several storage options, including JVM heap, off-heap memory, and disk. This lets developers focus on business logic without worrying as much about the separate caching concerns.
Ehcache works well for monolithic applications, and its developers also provide a clustering solution. This article focuses only on Ehcache in a monolithic application. In my view, distributed caching nowadays generally uses Redis, and fewer people use Ehcache’s clustering solution.
Usage
The example uses a very simple Spring Boot project.
Version
At the time of writing, the latest Ehcache version is 3. It differs substantially from version 2, and the two versions are incompatible. Ehcache 2 also provided ehcache-web, which cached entire web page responses through a Filter. It was very convenient for single-instance applications in the earlier Java EE era. Version 3 removed that feature because its developers considered it too specialized and outside Ehcache’s direction.
Configuration
Ehcache 3 supports both XML and programmatic configuration. The configuration options are the same in either form, so let us first look at the common settings.
Configuration Options
- cache alias: the cache name. An application may have multiple caches, and each needs a name.
- cache key type: the type of keys in a particular cache.
- cache value type: the type of values associated with the keys.
- cache expiry: Ehcache provides three policies—no expiry, timeToLive, and timeToIdle.
- cache resources: the maximum resources allocated to a cache and where they reside, such as heap, off-heap memory, or disk.
- cache listeners: listeners for events on cache entries, such as creation, removal, expiry, and updates.
These basic settings correspond closely to the concerns we considered when writing our own caches in earlier days.
Programmatic Configuration
@Slf4j
@Configuration
public class EhcacheConfiguration {
public static final String CACHE_NAME = "demo";
/**
* Expiry policy
* no expiry
* timeToLive
* timeToIdle-this means cache mappings will expire after a fixed duration following the time they were last accessed
* https://www.ehcache.org/documentation/3.9/expiry.html
*
* Storage tier choices:
* 1. Heap
* 2. Off-heap: define the resource pool yourself
* 3. Disk
* 4. Cluster
* https://www.ehcache.org/documentation/3.9/tiering.html
*
* Eviction policy:
* The official documentation says relatively little about Ehcache 3 eviction and notes that eviction can reduce efficiency. Some online explanations say Ehcache treats all cached objects as equivalent.
* https://www.ehcache.org/documentation/3.9/eviction-advisor.html
* @return org.ehcache.CacheManager
*/
@Bean
public CacheManager cacheManager(CacheEventListener<Object, Object> cacheEventListener) {
return initCacheManagerFromProgrammatic(cacheEventListener);
}
public CacheManager initCacheManagerFromProgrammatic(CacheEventListener<Object, Object> cacheEventListener) {
return CacheManagerBuilder.newCacheManagerBuilder()
.withCache(CACHE_NAME,
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, DataVO.class, ResourcePoolsBuilder.heap(2))
// Only one expiry policy applies; later settings override earlier ones
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(30)))
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMinutes(2)))
.withExpiry(ExpiryPolicy.NO_EXPIRY)
// Configure the listener
.withService(initCacheEventListenerConfigurationBuilder(cacheEventListener)))
.build(true);
}
/**
* Cache listener
* @param cacheEventListener
* @return
*/
private CacheEventListenerConfigurationBuilder initCacheEventListenerConfigurationBuilder(CacheEventListener<Object, Object> cacheEventListener) {
return CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(cacheEventListener, EventType.CREATED, EventType.EXPIRED, EventType.UPDATED, EventType.REMOVED)
.unordered()
.asynchronous();
}
}
XML Configuration
<?xml version="1.0" encoding="UTF-8"?>
<ehcache:config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:ehcache='http://www.ehcache.org/v3'
xsi:schemaLocation="http://www.ehcache.org/v3 https://www.ehcache.org/schema/ehcache-core-3.9.xsd">
<ehcache:cache alias="demo">
<ehcache:key-type>java.lang.Long</ehcache:key-type>
<ehcache:value-type>com.example.ehcache.vo.DataVO</ehcache:value-type>
<ehcache:expiry>
<ehcache:tti unit="minutes">1</ehcache:tti>
</ehcache:expiry>
<ehcache:listeners>
<ehcache:listener>
<ehcache:class>com.example.ehcache.config.CacheEventLogListener</ehcache:class>
<ehcache:event-firing-mode>ASYNCHRONOUS</ehcache:event-firing-mode>
<ehcache:event-ordering-mode>UNORDERED</ehcache:event-ordering-mode>
<!-- Define multiple event types -->
<ehcache:events-to-fire-on>CREATED</ehcache:events-to-fire-on>
<ehcache:events-to-fire-on>UPDATED</ehcache:events-to-fire-on>
<ehcache:events-to-fire-on>REMOVED</ehcache:events-to-fire-on>
<ehcache:events-to-fire-on>EXPIRED</ehcache:events-to-fire-on>
</ehcache:listener>
</ehcache:listeners>
<ehcache:resources>
<ehcache:heap unit="entries">10</ehcache:heap>
</ehcache:resources>
</ehcache:cache>
</ehcache:config>
Note: the listeners element must precede the resources element.
Custom Listener
Implement a custom listener using Ehcache’s CacheEventListener interface. It logs the event name, key, and value.
@Component
@Slf4j
public class CacheEventLogListener implements CacheEventListener<Object, Object> {
@Override
public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
log.info("cacheType is {}, key is {}, oldValue {}, newValue {}", cacheEvent.getType().toString(), cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
}
}
Verification
Example Code
Use a simple Controller to verify caching and the custom listener.
@RestController
@RequestMapping(path = "/data")
public class CommonDataController {
@PostMapping
public Long createDataVO(@RequestBody DataVO data) {
Random random = new Random();
Long result = random.nextLong();
data.setId(result);
Cache<Long, DataVO> cache = cacheManager.getCache(EhcacheConfiguration.CACHE_NAME, Long.class, DataVO.class);
cache.put(result, data);
return result;
}
@GetMapping(path = "/{id}")
public DataVO getCacheData(@PathVariable Long id) {
Cache<Long, DataVO> cache = cacheManager.getCache(EhcacheConfiguration.CACHE_NAME, Long.class, DataVO.class);
DataVO result;
result = cache.get(id);
if (Objects.isNull(result)) {
throw new RuntimeException("cache not exist");
}
return result;
}
}
Only part of the code is listed here. Test it with a POST endpoint and a GET endpoint.
curl -X POST -H 'Content-Type: application/json' http://localhost:18080/ehcache3/data
A successful request returns the new data ID: 8585661300356241871.

Request the data by ID:
curl -X GET http://localhost:18080/ehcache3/data/8585661300356241871
After the configured expiry policy expires the entry, request the same ID again. As in the first screenshot above, the custom listener logs the expiry event.
Improving the Code
Make a small improvement so that the application supports both XML and programmatic configuration. Enable a property to use XML; otherwise, use the default programmatic configuration.
Add this property to application.yml:
ehcache:
read-from-xml: true
The complete revised EhcacheConfiguration is:
@Slf4j
@Configuration
public class EhcacheConfiguration {
public static final String CACHE_NAME = "demo";
@Value("${ehcache.read-from-xml}")
private Boolean readFromXml;
/**
* Expiry policy
* no expiry
* timeToLive
* timeToIdle-this means cache mappings will expire after a fixed duration following the time they were last accessed
* https://www.ehcache.org/documentation/3.9/expiry.html
*
* Storage tier choices:
* 1. Heap
* 2. Off-heap: define the resource pool yourself
* 3. Disk
* 4. Cluster
* https://www.ehcache.org/documentation/3.9/tiering.html
*
* Eviction policy:
* The official documentation says relatively little about Ehcache 3 eviction and notes that eviction can reduce efficiency. Some online explanations say Ehcache treats all cached objects as equivalent.
* https://www.ehcache.org/documentation/3.9/eviction-advisor.html
* @return org.ehcache.CacheManager
*/
@Bean
public CacheManager cacheManager(CacheEventListener<Object, Object> cacheEventListener) {
CacheManager result;
if (readFromXml) {
result = initCacheManagerFromXml();
}else {
result = initCacheManagerFromProgrammatic(cacheEventListener);
}
return result;
}
private CacheManager initCacheManagerFromXml() {
URL resource = getClass().getResource("/ehcache.xml");
Objects.requireNonNull(resource);
XmlConfiguration xmlConfiguration = new XmlConfiguration(resource);
CacheManager result = CacheManagerBuilder.newCacheManager(xmlConfiguration);
result.init();
return result;
}
public CacheManager initCacheManagerFromProgrammatic(CacheEventListener<Object, Object> cacheEventListener) {
return CacheManagerBuilder.newCacheManagerBuilder()
.withCache(CACHE_NAME,
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, DataVO.class, ResourcePoolsBuilder.heap(2))
// Only one expiry policy applies; later settings override earlier ones
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(30)))
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMinutes(2)))
.withExpiry(ExpiryPolicy.NO_EXPIRY)
// Configure the listener
.withService(initCacheEventListenerConfigurationBuilder(cacheEventListener)))
.build(true);
}
/**
* Cache listener
* @param cacheEventListener
* @return
*/
private CacheEventListenerConfigurationBuilder initCacheEventListenerConfigurationBuilder(CacheEventListener<Object, Object> cacheEventListener) {
return CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(cacheEventListener, EventType.CREATED, EventType.EXPIRED, EventType.UPDATED, EventType.REMOVED)
.unordered()
.asynchronous();
}
}
That covers basic Ehcache usage.
Notes
Complete Example Code
Official Documentation
- Official documentation, where you can find more detailed configuration information.