Skip to content
JackSparrow414
Go back

Basic Ehcache Usage

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

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. Ehcache event listener logs showing cache creation and expiration events

Request the data by ID:

curl -X GET http://localhost:18080/ehcache3/data/8585661300356241871

Console output showing the cached value returned for an ID request 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


Share this post:

Previous Post
SpEL: Getting Started and Quick Reference
Next Post
Reading the Java EE Official Documentation

Comments

Questions, corrections, and experiences are welcome. Sign in with GitHub to comment; both language versions share this discussion.

Comments are available on the live site only.