Mastering Caffeine Cache in Spring Boot 3.2: Performance, Eviction & Async Usage
This article introduces the high‑performance Caffeine caching library for Spring Boot 3.2, explains its core features, demonstrates detailed performance benchmarks, and provides practical code examples covering synchronous and asynchronous operations, eviction policies, removal listeners, refresh mechanisms, and statistics collection.
1. Introduction
Caffeine is a high‑performance Java 8‑based caching library that offers near‑optimal hit rates. Unlike a plain ConcurrentMap, a Caffeine Cache can be configured to evict entries automatically, and its LoadingCache and AsyncLoadingCache provide convenient automatic loading.
Features
Automatic loading of elements, with optional asynchronous loading.
Capacity‑based eviction using near‑frequency algorithms.
Time‑based eviction based on last access or write.
Asynchronous refresh on stale reads.
Keys are wrapped with weak references.
Values can be wrapped with weak or soft references.
Eviction or removal notifications.
Write‑through to an external data source.
Cache access statistics collection.
2. Performance Test
Benchmarks were run with Java Microbenchmark Harness (JMH). The “generation” test measures the cost of locking when creating cache entries. The “read” test uses 8 threads reading from a cache with a maximum size. The “write” test uses 8 threads performing concurrent writes.
Read test (75% reads / 25% writes) uses 6 reader threads and 2 writer threads.
Write test (100% writes) uses 8 writer threads.
3. Practical Examples
3.1 Add Dependency
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>3.2 Adding Elements
Synchronous operations
static final Cache<Long, User> cache = Caffeine.newBuilder()
// expire after 10 minutes regardless of usage
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
public static void basicCache() {
User user = cache.getIfPresent(1L);
user = cache.get(1L, key -> new User(key, "Zhang San", 22));
cache.put(1L, user);
cache.invalidate(1L);
}Asynchronous operations
static AsyncCache<Long, User> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.buildAsync();
public static void asyncCache() {
CompletableFuture<User> cf = cache.getIfPresent(1L);
cf.thenAccept(System.out::println);
cf = cache.get(1L, key -> new User(key, "Zhang San", 22));
cache.put(1L, CompletableFuture.supplyAsync(() -> new User(1L, "Li Si", 33)));
cache.synchronous().invalidate(1L);
}3.3 Eviction Strategies
Caffeine provides three eviction policies: capacity‑based, time‑based, and reference‑based.
Capacity‑based – evicts when the maximum size is exceeded, using near‑frequency algorithms.
public static User createUser(Long key) {
return new User(key, "name - " + key, new Random().nextInt(100));
}
public static void main(String[] args) {
LoadingCache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(3)
.build(key -> createUser(key));
System.out.println(cache.get(1L));
System.out.println(cache.get(2L));
System.out.println(cache.get(3L));
System.out.println(cache.get(4L)); // evicts key 2
}Result shows key 2 being evicted when key 4 is inserted.
Time‑based – expireAfterAccess, expireAfterWrite, or custom Expiry.
Cache<Long, User> cache = Caffeine.newBuilder()
.expireAfterAccess(3, TimeUnit.SECONDS)
.build();
cache.put(1L, new User(1L, "Admin", 20));
System.out.println(cache.getIfPresent(1L));
TimeUnit.SECONDS.sleep(3);
System.out.println(cache.getIfPresent(1L)); // null after 3 sReference‑based – keys are weakly referenced, values can be weak or soft referenced (not detailed here).
3.4 Removal
static final Cache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(10)
.build();
public static void remove(Long key) {
cache.invalidate(key);
cache.invalidateAll(Arrays.asList(key));
cache.invalidateAll();
}Listeners can be attached to receive eviction or removal callbacks.
static Cache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(3)
.evictionListener((k, v, cause) -> System.out.printf("Key %s was evicted (%s)%n", k, cause))
.removalListener((k, v, cause) -> System.out.printf("Key %s was removed (%s)%n", k, cause))
.build();3.5 Refresh
static final Cache<Long, User> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.refreshAfterWrite(3, TimeUnit.SECONDS)
.maximumSize(10)
.build(key -> createUser(key));
private static User createUser(Long key) {
int r = new Random().nextInt(100);
return new User(key, "China - " + r, r);
}Refresh occurs only when the key is accessed after the refresh interval.
3.6 Statistics
static final Cache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(10)
.recordStats()
.build();
CacheStats stats = cache.stats();
System.out.println("hitRate: " + stats.hitRate());
System.out.println("evictionCount: " + stats.evictionCount());
System.out.println("averageLoadPenalty: " + stats.averageLoadPenalty());4. Comprehensive Example
For a full‑stack demonstration of Caffeine in a real Spring Boot project, refer to the linked article “Annotation + Caffeine: High‑Performance API Rate Limiting”.
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.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.
