High-Performance Local Cache Solutions Compared: Guava, Caffeine, Ehcache & ConcurrentHashMap

This article compares four Java local caching solutions — ConcurrentHashMap, Guava Cache, Caffeine, and Ehcache — detailing their features, performance characteristics, and code examples, while addressing cache consistency challenges in two-level cache architectures using MQ or Canal for synchronization.

Architect's Guide
Architect's Guide
Architect's Guide
High-Performance Local Cache Solutions Compared: Guava, Caffeine, Ehcache & ConcurrentHashMap

Background

In high-performance service architectures, caching is essential. Hot data is typically stored in remote caches like Redis or Memcached, falling back to the database on cache misses. This improves access speed and reduces database load.

As systems evolve, a two-level cache architecture emerges: a local cache (e.g., Guava Cache, Caffeine) as L1 and a remote cache (Redis) as L2. The access flow checks L1 first, then L2, then the database.

Two-level cache architecture diagram
Two-level cache architecture diagram

Why Use Local Cache

Local cache resides in JVM heap memory, offering extremely fast access. Suitable for data with low change frequency and relaxed real-time requirements.

Reduces network I/O overhead and latency by minimizing interactions with remote caches like Redis.

Required Features for a Local Cache

Storage with read/write operations

Atomic operations (thread safety), e.g., ConcurrentHashMap

Maximum capacity limit

Eviction policies when capacity exceeded (LRU, LFU)

Time-based expiration (scheduled, lazy, periodic)

Persistence support

Statistics and monitoring

Local Cache Solution Comparison

1. ConcurrentHashMap

Basic thread-safe map. Simple, no third-party dependency. Suitable for simple scenarios. Lacks built-in eviction, expiration, statistics; custom development needed for advanced features, increasing cost and risk.

2. Guava Cache

Google's core Java library. Mature, stable, widely used. Features:

Maximum capacity limit

Two expiration policies: expire after write, expire after access

Basic statistics

LRU-based eviction

Maven dependency:

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>31.1-jre</version>
</dependency>

Usage example:

@Slf4j
public class GuavaCacheTest {
  public static void main(String[] args) throws ExecutionException {
    Cache<String, String> cache = CacheBuilder.newBuilder()
      .initialCapacity(5)
      .maximumSize(10)
      .expireAfterWrite(60, TimeUnit.SECONDS)
      .build();

    String orderId = String.valueOf(123456789);
    String orderInfo = cache.get(orderId, () -> getInfo(orderId));
    log.info("orderInfo = {}", orderInfo);
  }

  private static String getInfo(String orderId) {
    String info = "";
    log.info("get data from redis");
    log.info("get data from mysql");
    info = String.format("{orderId=%s}", orderId);
    return info;
  }
}

3. Caffeine

Next-generation cache for Java 8+, performance near theoretical optimum. Enhanced version of Guava Cache with similar API but uses W-TinyLFU algorithm (combines LRU and LFU advantages), delivering superior performance.

Maven dependency:

<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
  <version>2.9.3</version>
</dependency>

Usage example:

@Slf4j
public class CaffeineTest {
  public static void main(String[] args) {
    Cache<String, String> cache = Caffeine.newBuilder()
      .initialCapacity(5)
      .maximumSize(10)
      .expireAfterWrite(60, TimeUnit.SECONDS)
      .build();

    String orderId = String.valueOf(123456789);
    String orderInfo = cache.get(orderId, key -> getInfo(key));
    System.out.println(orderInfo);
  }

  private static String getInfo(String orderId) {
    String info = "";
    log.info("get data from redis");
    log.info("get data from mysql");
    info = String.format("{orderId=%s}", orderId);
    return info;
  }
}

4. Ehcache

Pure Java in-process cache framework, fast and lightweight. Default CacheProvider for Hibernate. Richer features and extensibility than Guava/Caffeine:

Multiple eviction algorithms: LRU, LFU, FIFO

Three storage tiers: heap, off-heap, disk (supports persistence)

Cluster solutions for data sharing

Maven dependency:

<dependency>
  <groupId>org.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>3.9.7</version>
</dependency>

Usage example:

@Slf4j
public class EhcacheTest {
  private static final String ORDER_CACHE = "orderCache";
  public static void main(String[] args) {
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
      .withCache(ORDER_CACHE, CacheConfigurationBuilder
        .newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20)))
      .build(true);

    Cache<String, String> cache = cacheManager.getCache(ORDER_CACHE, String.class, String.class);
    String orderId = String.valueOf(123456789);
    String orderInfo = cache.get(orderId);
    if (StrUtil.isBlank(orderInfo)) {
      orderInfo = getInfo(orderId);
      cache.put(orderId, orderInfo);
    }
    log.info("orderInfo = {}", orderInfo);
  }

  private static String getInfo(String orderId) {
    String info = "";
    log.info("get data from redis");
    log.info("get data from mysql");
    info = String.format("{orderId=%s}", orderId);
    return info;
  }
}

Local Cache Issues and Solutions

1. Cache Consistency

In a two-level cache, local cache, remote cache, and database must stay consistent. On data modification, both caches should be updated synchronously.

Solution 1: Message Queue (Broadcast)

In clustered deployments, multiple nodes have separate local caches. Use MQ broadcast mode: on data change, publish message; each node consumes and invalidates its local cache, achieving eventual consistency.

MQ broadcast consistency diagram
MQ broadcast consistency diagram

Solution 2: Canal + MQ

Avoid business-code MQ calls by subscribing to database binlog. Canal captures MySQL binlog changes, publishes to MQ, which then triggers cache invalidation.

Canal + MQ consistency diagram
Canal + MQ consistency diagram

2. Technical Selection Criteria

Usability: Guava Cache, Caffeine, and Ehcache all offer mature integration and simple APIs.

Functionality: Guava and Caffeine support heap-only storage; Ehcache adds off-heap, disk storage, persistence, and clustering.

Performance: Caffeine outperforms Guava Cache, which outperforms Ehcache (see benchmark chart).

Performance comparison chart: Caffeine > Guava Cache > Ehcache
Performance comparison chart: Caffeine > Guava Cache > Ehcache

Recommendation: Use Caffeine for local cache (best performance). For distributed caching, use Redis or Memcached. Build a multi-level cache system for both performance and reliability.

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.

Javacachingcache-consistencyCaffeinelocal cacheperformance comparisonEhcachetwo-level cacheGuava Cache
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.