Mastering Ehcache: A Deep Dive into Java Local Caching Strategies

This article introduces Ehcache, an open‑source Java local‑cache framework, explains its layered storage options, flexible expiration policies, eviction strategies, and provides comprehensive code examples for configuring in‑memory, off‑heap, and disk‑persistent caches, along with practical usage tips and performance considerations.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
Mastering Ehcache: A Deep Dive into Java Local Caching Strategies

Java local caching includes frameworks like Caffeine, Guava Cache, and Ehcache. While Caffeine is popular, Ehcache offers fast, flexible caching with memory and disk options and rich configuration.

1. What is Ehcache

Ehcache is an open‑source Java local‑cache framework that provides fast, flexible caching solutions. It supports in‑memory and disk storage, integrates with various Java applications including Spring, offers rich configuration options, and can work with distributed caches such as Terracotta.

2. Features of Ehcache

Layered storage:

On‑heap storage: Uses JVM heap RAM; fast but limited and subject to GC pauses.

Off‑heap storage: Uses RAM outside the JVM heap; not affected by GC, slightly slower.

Disk storage: Persists data to the file system; abundant space but slower than RAM; SSD recommended.

Clustered (distributed) storage: Remote server cache; incurs network latency and consistency overhead.

Flexible expiration policies:

No expiration – entries live as long as the application runs.

Time‑to‑live – entries expire after a fixed duration since creation.

Idle time – entries expire after a period of inactivity.

Custom expiration – implement ExpiryPolicy to define bespoke rules.

Expiration return values: Duration.ZERO – immediate expiration. Duration.INFINITE – never expires. Duration.ofSeconds(...) – expires after the specified time.

Null duration – keeps the previous expiration setting.

Eviction strategies:

LFU – evicts entries with low access frequency.

LRU – evicts least recently used entries.

FIFO – evicts entries that entered the cache earliest.

3. Cache architecture

Typical three‑tier architecture: on‑heap, off‑heap, and disk storage.

Ehcache architecture diagram
Ehcache architecture diagram

The cache manager, cache, and element form the core of Ehcache. Transactions ensure consistency across tiers, and listeners clean up or persist data according to eviction policies.

4. Practical usage

Dependency (Maven):

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.10.0</version>
</dependency>
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>

Creating caches:

/*************************** 1. Pure in‑memory cache *****************************/
 // 1.1 Create a pre‑configured in‑memory cache
 CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
         .withCache("preConfigured",
                 CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                         ResourcePoolsBuilder.heap(10)))
         .build(true);

 // 1.2 Retrieve the cache instance
 Cache<Long, String> preConfigured = cacheManager.getCache("preConfigured", Long.class, String.class);

/*************************** 2. Add a new cache *****************************/
 // 2.1 Create and obtain a new cache
 Cache<Long, String> myCache = cacheManager.createCache("myCache",
         CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                 ResourcePoolsBuilder.heap(10)));

/*************************** 3. Three‑tier persistent cache *****************************/
 // 3.1 Create a cache with heap → off‑heap → disk persistence
 PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
         .with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/", "myData")))
         .withCache("threeTieredCache",
                 CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                         ResourcePoolsBuilder.newResourcePoolsBuilder()
                                 .heap(10, EntryUnit.ENTRIES)
                                 .offheap(1, MemoryUnit.MB)
                                 .disk(20, MemoryUnit.MB, true)))
         .build(true);

 // 3.2 Obtain the three‑tier cache
 Cache<Long, String> threeTieredCache = persistentCacheManager.getCache("threeTieredCache",
         Long.class, String.class);

/*************************** 4. One manager, multiple persistent caches *****************************/
 // 4.1 Manage multiple caches, each persisted to its own directory
 PersistentCacheManager persistentCacheManager1 = CacheManagerBuilder.newCacheManagerBuilder()
         .with(CacheManagerBuilder.persistence(new File("/path/to/persistent/directory1").getAbsoluteFile()))
         .withCache("cache1",
                 CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                         ResourcePoolsBuilder.newResourcePoolsBuilder()
                                 .heap(10, EntryUnit.ENTRIES)
                                 .offheap(1, MemoryUnit.MB)
                                 .disk(20, MemoryUnit.MB, true)))
         .with(CacheManagerBuilder.persistence(new File("/path/to/persistent/directory2").getAbsoluteFile()))
         .withCache("cache2",
                 CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Integer.class,
                         ResourcePoolsBuilder.newResourcePoolsBuilder()
                                 .heap(20, EntryUnit.ENTRIES)
                                 .offheap(2, MemoryUnit.MB)
                                 .disk(30, MemoryUnit.MB, true)))
         .build(true);

Typical usage patterns combine tiers, e.g., heap + off‑heap + disk, or just heap + off‑heap, etc. When using disk persistence, remember to close the manager before application exit to flush data; on restart, persisted data is reloaded, reducing database load.

5. Test results

Cache with disk persistence works as expected; entries expire according to configured TTL, and data is correctly reloaded after a restart.

Test result
Test result

6. Full test code

package com.jx.jxreserve.groupbuy.manager;

import com.jd.flash.commons.exception.BaseBusinessException;
import com.jx.jxreserve.groupbuy.common.enums.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.PersistentCacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.springframework.stereotype.Component;

import java.io.File;
import java.time.Duration;

import static org.ehcache.Status.AVAILABLE;

/**
 * Ehcache test manager
 */
@Slf4j
@Component
public class EhcacheTestManager {

    /*************************** 1. Pure in‑memory operation *****************************/
    // 1.1 Create cache preConfigured based on heap memory
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            .withCache("preConfigured",
                    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                            ResourcePoolsBuilder.heap(10)))
            .build(true);

    // 1.2 Get cache instance
    Cache<Long, String> preConfigured = cacheManager.getCache("preConfigured", Long.class, String.class);

    /*************************** 2. Add new instance *****************************/
    // 2.1 Create a new cache and obtain it
    Cache<Long, String> myCache = cacheManager.createCache("myCache",
            CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                    ResourcePoolsBuilder.heap(10)));

    /*************************** 3. Three‑tier storage – disk persistence *****************************/
    // 3.1 Create cache myData with heap → off‑heap → local disk
    PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            .with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/", "myData")))
            .withCache("threeTieredCache",
                    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                            ResourcePoolsBuilder.newResourcePoolsBuilder()
                                    .heap(10, EntryUnit.ENTRIES)
                                    .offheap(1, MemoryUnit.MB)
                                    .disk(20, MemoryUnit.MB, true))
                    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(600)))) // set TTL
            .build(true);

    /*************************** 4. One manager, multiple caches – disk persistence *****************************/
    // 4.1 Manage multiple caches, each persisted to its own directory
    PersistentCacheManager persistentCacheManager1 = CacheManagerBuilder.newCacheManagerBuilder()
            .with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/").getAbsoluteFile()))
            .withCache("cache1",
                    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                            ResourcePoolsBuilder.newResourcePoolsBuilder()
                                    .heap(10, EntryUnit.ENTRIES)
                                    .offheap(1, MemoryUnit.MB)
                                    .disk(20, MemoryUnit.MB, true)))
            .with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/").getAbsoluteFile()))
            .withCache("cache2",
                    CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Integer.class,
                            ResourcePoolsBuilder.newResourcePoolsBuilder()
                                    .heap(20, EntryUnit.ENTRIES)
                                    .offheap(2, MemoryUnit.MB)
                                    .disk(30, MemoryUnit.MB, true)))
            .build(true);

    /** Set cache value */
    public void setEhCache(Long key, String values) throws BaseBusinessException {
        try {
            log.info("setEhCache.value:{},{}", values, key);
            Cache<Long, String> testDiskCache = getManagerCache("testDiskCache");
            testDiskCache.put(key, values);
        } catch (Exception e) {
            log.error("setEhCache failure! Exception:", e);
            throw new BaseBusinessException(ErrorCode.SYSTEM_DB_ERROR.getCode(),
                    ErrorCode.SYSTEM_DB_ERROR.getMessage());
        }
    }

    /** Get cache value */
    public String getEhCache(Long key) throws BaseBusinessException {
        try {
            log.info("getEhCache.key:{}", key);
            Cache<Long, String> testDiskCache = getManagerCache("testDiskCache");
            return testDiskCache.get(key);
        } catch (Exception e) {
            log.error("getEhCache failure! Exception:", e);
            throw new BaseBusinessException(ErrorCode.SYSTEM_DB_ERROR.getCode(),
                    ErrorCode.SYSTEM_DB_ERROR.getMessage());
        }
    }

    /** Close cache manager */
    public void closeEhCache() throws BaseBusinessException {
        try {
            log.info("closeEhCache.persistentCacheManager.close1:{}", persistentCacheManager.getStatus());
            persistentCacheManager.close();
            log.info("closeEhCache.persistentCacheManager.close2:{}", persistentCacheManager.getStatus());
        } catch (Exception e) {
            log.error("closeEhCache failure! Exception:", e);
            throw new BaseBusinessException(ErrorCode.SYSTEM_DB_ERROR.getCode(),
                    ErrorCode.SYSTEM_DB_ERROR.getMessage());
        }
    }

    private Cache<Long, String> getManagerCache(String cache) {
        // Ensure manager is available
        if (AVAILABLE != persistentCacheManager.getStatus()) {
            persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                    .with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/", "groupData")))
                    .withCache("testDiskCache",
                            CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                                    ResourcePoolsBuilder.newResourcePoolsBuilder()
                                            .heap(10, EntryUnit.ENTRIES)
                                            .offheap(1, MemoryUnit.MB)
                                            .disk(20, MemoryUnit.MB, true)))
                    .build(true);
        }
        return persistentCacheManager.getCache(cache, Long.class, String.class);
    }
}

Note: For production systems requiring distributed caching, Redis is generally recommended over Ehcache’s Terracotta‑based clustering due to better reliability and performance.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaspringcachingEhcacheCache Persistence
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.