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.
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.
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.
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.
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).
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
