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.
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<String, String> 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<String, String> loadingCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@Override public String load(@NonNull String key) { return createExpensiveValue(); }
@Override public @NonNull Map<String, String> loadAll(@NonNull Iterable<? extends String> keys) {
Map<String, String> map = new HashMap<>();
for (String key : keys) { map.put(key, createExpensiveValue()); }
return map;
}
});
String a = loadingCache.get("a");
System.out.println(a);
Set<String> keys = new HashSet<>();
keys.add("a"); keys.add("b");
Map<String, String> 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<String, String> asyncCache = Caffeine.newBuilder()
.maximumSize(100)
.buildAsync();
asyncCache.put("a", CompletableFuture.completedFuture("a"));
CompletableFuture<String> a = asyncCache.getIfPresent("a");
System.out.println(a.get());
CompletableFuture<String> 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<String, String> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.buildAsync((key, executor) -> createExpensiveValueAsync(key, executor));
CompletableFuture<String> a = cache.get("a");
System.out.println(a.get());
Set<String> keys = new HashSet<>();
keys.add("a"); keys.add("b");
CompletableFuture<Map<String, String>> values = cache.getAll(keys);
System.out.println(values.get());
}
private static CompletableFuture<String> 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());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.
JD Tech Talk
Official JD Tech public account delivering best practices and technology innovation.
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.
