Backend Development 12 min read

Understanding Caffeine: A High‑Performance Java Caching Library

This article introduces the Caffeine Java caching library, detailing its features, core classes and parameters, various loading strategies—including manual, automatic, and asynchronous loading—eviction policies, removal listeners, and cache statistics, accompanied by practical code examples for integration in backend applications.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Understanding Caffeine: A High‑Performance Java Caching Library

Understanding Caffeine

Caffeine is a Java‑8 based high‑performance caching library that offers near‑optimal hit rates and is the default local cache implementation in Spring Boot. It provides flexible builders to create caches with automatic loading (optional async), size‑based eviction, time‑based expiration, asynchronous refresh, weak/soft references for keys and values, eviction notifications, and access statistics.

Automatic loading of entries (optional asynchronous)

Size‑based eviction

Expiration after write or access

Asynchronous refresh

Weakly referenced keys and weak/softly referenced values

Eviction notifications

Cache access statistics

Core classes include Caffeine for building caches and parameters such as maximumSize , maximumWeight , initialCapacity , expireAfterWriteNanos , expireAfterAccessNanos , and refreshAfterWriteNanos .

Data Loading Strategies

Caffeine supports four loading strategies:

Manual loading

public static void demo() {
    Cache
cache = Caffeine.newBuilder()
            .expireAfterAccess(Duration.ofMinutes(1))
            .maximumSize(100)
            .recordStats()
            .build();
    cache.put("a", "a");
    String a = cache.getIfPresent("a");
    System.out.println(a);
    String b = cache.get("b", k -> {
        System.out.println("begin query ..." + Thread.currentThread().getName());
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("end query ...");
        return UUID.randomUUID().toString();
    });
    System.out.println(b);
    cache.invalidate("a");
}

Automatic loading

public static void demo() {
    LoadingCache
loadingCache = Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(new CacheLoader
() {
                @Override public String load(@NonNull String key) { return createExpensiveValue(); }
                @Override public @NonNull Map
loadAll(@NonNull Iterable
keys) {
                    Map
map = new HashMap<>();
                    for (String key : keys) { map.put(key, createExpensiveValue()); }
                    return map;
                }
            });
    String a = loadingCache.get("a");
    System.out.println(a);
    Set
keys = new HashSet<>();
    keys.add("a"); keys.add("b");
    Map
all = loadingCache.getAll(keys);
    System.out.println(all);
}
private static String createExpensiveValue() {
    System.out.println("begin query ..." + Thread.currentThread().getName());
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    System.out.println("end query ...");
    return UUID.randomUUID().toString();
}

Manual asynchronous loading

public static void demo() throws ExecutionException, InterruptedException {
    AsyncCache
asyncCache = Caffeine.newBuilder()
            .maximumSize(100)
            .buildAsync();
    asyncCache.put("a", CompletableFuture.completedFuture("a"));
    CompletableFuture
a = asyncCache.getIfPresent("a");
    System.out.println(a.get());
    CompletableFuture
b = asyncCache.get("b", k -> createExpensiveValue("b"));
    System.out.println(b.get());
    asyncCache.synchronous().invalidate("a");
    System.out.println(asyncCache.getIfPresent("a"));
}
private static String createExpensiveValue(String key) {
    System.out.println("begin query ..." + Thread.currentThread().getName());
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    System.out.println("end query ...");
    return UUID.randomUUID().toString();
}

Automatic asynchronous loading

public static void demo() throws ExecutionException, InterruptedException {
    AsyncLoadingCache
cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .buildAsync((key, executor) -> createExpensiveValueAsync(key, executor));
    CompletableFuture
a = cache.get("a");
    System.out.println(a.get());
    Set
keys = new HashSet<>();
    keys.add("a"); keys.add("b");
    CompletableFuture
> values = cache.getAll(keys);
    System.out.println(values.get());
}
private static CompletableFuture
createExpensiveValueAsync(String key, Executor executor) {
    System.out.println("begin query ..." + Thread.currentThread().getName());
    try { Thread.sleep(1000); executor.execute(() -> System.out.println("async create value....")); } catch (InterruptedException e) {}
    System.out.println("end query ...");
    return CompletableFuture.completedFuture(UUID.randomUUID().toString());
}

Eviction Strategies

Caffeine offers three eviction strategies: size‑based, time‑based, and reference‑based, plus manual removal and listeners.

Size‑based eviction using .maximumSize() or .maximumWeight()

Time‑based eviction via .expireAfterAccess() , .expireAfterWrite() , or a custom Expiry implementation

Reference‑based eviction with .weakKeys() , .weakValues() , and .softValues()

Manual removal can be performed with cache.invalidate(key) , cache.invalidateAll(keys) , or cache.invalidateAll() . Removal listeners can be registered via .removalListener() or .evictionListener() to react to eviction causes such as EXPLICIT, REPLACED, COLLECTED, EXPIRED, or SIZE.

Cache Statistics

Enabling .recordStats() allows collection of detailed metrics such as hit count, miss count, load success/failure counts, total load time, eviction count, and request count.

CacheStats stats = cache.stats();
System.out.println("stats.hitCount():" + stats.hitCount());
System.out.println("stats.hitRate():" + stats.hitRate());
System.out.println("stats.missCount():" + stats.missCount());
System.out.println("stats.missRate():" + stats.missRate());
System.out.println("stats.loadSuccessCount():" + stats.loadSuccessCount());
System.out.println("stats.loadFailureCount():" + stats.loadFailureCount());
System.out.println("stats.totalLoadTime():" + stats.totalLoadTime());
System.out.println("stats.evictionCount():" + stats.evictionCount());
System.out.println("stats.requestCount():" + stats.requestCount());
System.out.println("stats.averageLoadPenalty():" + stats.averageLoadPenalty());
BackendJavaPerformanceCachecaffeineasync
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

0 followers
Reader feedback

How this landed with the community

login 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.