Choosing the Right Local Cache in Java: From ConcurrentHashMap to Caffeine
This article examines why and how to use local in‑process caches in Java, compares four implementations—ConcurrentHashMap, Guava Cache, Caffeine, and Ehcache—covers essential cache features, consistency challenges, hit‑rate optimization, and recommends Caffeine as the most performant choice for a two‑level caching architecture.
Background
In high‑performance service architectures, caching is essential. Hot data is usually stored in remote caches such as Redis or Memcached, and the database is queried only on a cache miss.
When remote caches become a bottleneck, a local (in‑process) cache can be added as a first‑level cache, forming a two‑level cache architecture that further reduces latency.
Why Use a Local Cache
Local memory access is extremely fast, suitable for low‑frequency‑change data.
It reduces network I/O by avoiding unnecessary round‑trips to remote caches.
Required Features of a Local Cache
Basic put/get operations.
Thread‑safe atomic operations (e.g., ConcurrentHashMap).
Maximum size limit.
Eviction policies such as LRU or LFU.
Expiration strategies (time‑based, lazy, periodic).
Optional persistence.
Statistics and monitoring.
Local Cache Options
1. ConcurrentHashMap
Implements a simple KV store with thread safety but requires custom code for eviction, size limits and expiration, making it suitable only for very simple scenarios.
2. Guava Cache
Google’s Guava library provides a ready‑made cache with maximum size, time‑based eviction, simple statistics and an LRU implementation.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
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));
System.out.println(orderInfo);
}
private static String getInfo(String orderId) {
// query Redis, then DB if necessary
return String.format("{orderId=%s}", orderId);
}
}3. Caffeine
Caffeine is a Java‑8‑based cache that outperforms Guava by using the W‑TinyLFU algorithm, offering similar features with higher throughput.
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
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);
}
// getInfo same as above
}4. Encache (Ehcache)
Ehcache offers multiple storage tiers (heap, off‑heap, disk) and various eviction algorithms, plus clustering support, but its performance is lower than Caffeine.
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.7</version>
</dependency>
public class EhcacheTest {
public static void main(String[] args) {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("orderCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class, ResourcePoolsBuilder.heap(20)))
.build(true);
Cache<String, String> cache = cacheManager.getCache("orderCache", String.class, String.class);
// use cache...
}
}Cache Consistency and Hit‑Rate Improvements
1. Consistency
Two‑level caches must stay consistent with the database. Typical solutions include broadcasting invalidation messages via MQ or using Canal to capture MySQL binlog changes and propagate them.
2. Improving Hit Rate
Techniques such as proper key design, appropriate expiration settings, and warm‑up loading can increase local cache hit ratios.
Choosing the Best Implementation
Ease of use: Guava, Caffeine, and Ehcache are all easy to integrate.
Feature set: Guava and Caffeine provide similar in‑heap caching; Ehcache adds off‑heap and disk persistence.
Performance: Benchmarks show Caffeine > Guava > Ehcache.
For most scenarios, Caffeine is recommended as the primary local cache, combined with Redis or Memcached as the second‑level distributed cache.
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
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
