Why Caffeine Outperforms Guava: Deep Dive into Java Caching, Benchmarks & Migration

This article explains Caffeine's high‑performance local cache features, presents JMH benchmark results that show it beating Guava, ConcurrentMap and ehcache, details its expiration, refresh and eviction mechanisms, compares memory usage and eviction algorithms, and provides migration code for Guava users.

Programmer DD
Programmer DD
Programmer DD
Why Caffeine Outperforms Guava: Deep Dive into Java Caching, Benchmarks & Migration

Stress Test

Using the JMH benchmark tool, Caffeine was compared with ConcurrentMap, GuavaCache, ehcache and others. The results demonstrate that Caffeine consistently outperforms the alternatives in pure‑read, pure‑write, and mixed workloads on both 8‑thread and 16‑thread configurations.

More benchmark comparisons are available at the Caffeine GitHub wiki.

Introduction

Caffeine is a high‑performance local cache library for JDK 8 that offers near‑perfect hit rates. While it implements the ConcurrentMap interface, it differs by providing automatic eviction policies to protect applications and limit memory consumption.

Key features include automatic loading (with optional async), size‑based eviction, time‑based expiration (from last write or access), asynchronous refresh, weak keys, weak or soft values, removal notifications, write‑through broadcasting, and cache statistics.

Automatic loading of data into the cache, optionally asynchronous.

Size‑based eviction policies.

Time‑based eviction from the last write or access.

Asynchronous refresh.

Keys wrapped as weak references.

Values wrapped as weak or soft references to allow GC.

Removal notifications.

Write‑through broadcast mechanism.

Cache access statistics.

Usage

Caffeine is straightforward to use, especially for developers familiar with GuavaCache. First, add the Maven dependency:

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

Then build a cache:

Cache<String, String> cache = Caffeine.newBuilder()
        .maximumSize(1024)               // size limit
        .expireAfterWrite(5, TimeUnit.MINUTES) // expiration
        .weakKeys()                      // weak keys
        .weakValues()                    // weak values
        .removalListener((RemovalListener<String, String>) (key, value, cause) ->
                System.out.println("key:" + key + ", value:" + value + ", removal cause:" + cause))
        .build();

cache.put("username", "afei");
cache.put("password", "123456");
System.out.println(cache.getIfPresent("username"));
System.out.println(cache.getIfPresent("password"));
System.out.println(cache.get("blog", key -> getValue(key)));

Asynchronous loading cache example:

AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
        .maximumSize(2)
        .expireAfterWrite(5, TimeUnit.MINUTES)
        .refreshAfterWrite(1, TimeUnit.MINUTES)
        .buildAsync(new CacheLoader<String, String>() {
            @Override
            public String load(@NonNull String key) throws Exception {
                return getValue(key);
            }
        });
System.out.println(cache.get("username").get());
System.out.println(cache.get("password").get(10, TimeUnit.MINUTES));

Expiration Mechanism

Expiration is essential for local caches to keep memory usage low while maintaining high hit rates. Caffeine supports three expiration strategies declared during cache construction:

expireAfterWrite – expires a entry a fixed duration after the last write.

expireAfterAccess – expires a entry a fixed duration after the last read or write.

expireAfter – custom expiration policy.

Refresh Mechanism

Refresh in Caffeine is passive. When the defined interval passes, the next read triggers a refresh, meaning stale data may be returned until the refresh completes.

Eviction Mechanism

Removal listeners can track evicted entries. The possible RemovalCause values are:

EXPLICIT – explicit removal via invalidate methods.

REPLACED – entry replaced by a new value.

COLLECTED – key or value reclaimed by GC.

EXPIRED – entry expired by time‑based policies.

SIZE – entry evicted because the cache exceeded its maximum size.

GuavaCache vs Caffeine Differences

Guava uses LRU eviction; Caffeine uses the Window TinyLFU algorithm.

Guava treats immediate expiration as a size‑zero cache, causing SIZE removal cause; Caffeine correctly reports EXPIRED.

Guava always fires removal listeners on replacement; Caffeine does not fire when the new value is the same instance.

Caffeine delegates many background tasks (eviction, refresh, listeners) to a thread pool (default ForkJoinPool.commonPool()).

Memory Usage Comparison

Caffeine can lazily initialize or dynamically adjust its internal data structures, reducing memory overhead. The benchmark image below shows memory consumption measured with Gradle's memoryOverhead tool.

LRU vs W‑TinyLFU

LRU (Least Recently Used) evicts the least recently accessed items, which works well but cannot predict future accesses. W‑TinyLFU combines recency with frequency using a “popularity sketch” (e.g., Count‑Min Sketch) to admit only items that are likely to be hot, and employs a segmented LRU (SLRU) for long‑term retention.

Guava Migration

Caffeine provides an adapter that lets you use Guava's LoadingCache API directly:

// Guava's LoadingCache interface
LoadingCache<Key, Graph> graphs = CaffeinatedGuava.build(
    Caffeine.newBuilder().maximumSize(10_000),
    new CacheLoader<Key, Graph>() {
        @Override
        public Graph load(Key key) throws Exception {
            return createExpensiveGraph(key);
        }
    });

References

https://github.com/ben-manes/caffeine/wiki/Design

http://highscalability.com/blog/2016/1/25/design-of-a-modern-cache.html

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.

JavaperformanceCacheCaffeineGuavaBenchmark
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.