Comprehensive Guide to Using Caffeine Cache in Java and Spring Boot
This article provides an in‑depth tutorial on the Caffeine local cache library for Java, covering its fundamentals, loading strategies, eviction policies, refresh mechanisms, statistics, and step‑by‑step integration with Spring Boot, including configuration, annotations, and practical code examples.
1. Introduction to Caffeine
Caffeine is a high‑performance local caching library for Java 8, derived from Guava and now the default cache implementation in Spring 5. It offers O(1) access time, concurrent support, and automatic eviction of rarely used entries based on configurable policies.
1.1 Cache Basics
Caching is a space‑for‑time trade‑off that speeds up data access. In Java, common cache solutions include EhCache, Memcached, and Redis for distributed caching. Caffeine serves as an excellent local cache alternative.
1.2 Adding Caffeine Dependency
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.0.5</version>
</dependency>2. Cache Loading Strategies
2.1 Manual Cache
Use put() to insert entries and getIfPresent() to retrieve them. get(key, k->value) atomically loads a missing value, while invalidate() removes an entry.
Cache<Object, Object> cache = Caffeine.newBuilder()
.initialCapacity(10)
.maximumSize(10)
.expireAfterWrite(1, TimeUnit.SECONDS)
.expireAfterAccess(1, TimeUnit.SECONDS)
.removalListener((k, v, cause) -> { })
.recordStats()
.build();
cache.put("1", "张三");
System.out.println(cache.getIfPresent("1"));
System.out.println(cache.get("2", k -> "默认值"));2.2 LoadingCache (Automatic Loading)
Define a CacheLoader so that get() automatically loads missing values. getAll() iterates over keys and invokes loadAll() if implemented.
LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
.refreshAfterWrite(10, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.SECONDS)
.expireAfterAccess(10, TimeUnit.SECONDS)
.maximumSize(10)
.build(key -> new Date().toString());2.3 AsyncCache (Asynchronous Retrieval)
AsyncCache returns CompletableFuture for non‑blocking access. The default executor is ForkJoinPool.commonPool(), but a custom executor can be supplied via Caffeine.executor(Executor).
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.SECONDS)
.expireAfterWrite(1, TimeUnit.SECONDS)
.expireAfterAccess(1, TimeUnit.SECONDS)
.maximumSize(10)
.buildAsync(key -> {
Thread.sleep(1000);
return new Date().toString();
});
CompletableFuture<String> future = asyncLoadingCache.get("1");
future.thenAccept(System.out::println);3. Eviction Policies
Caffeine supports size‑based, weight‑based, time‑based, and reference‑based eviction. Common strategies include LRU (least recently used), LFU (least frequently used), and FIFO (first in, first out). Example configuration:
Caffeine.newBuilder()
.maximumSize(10) // size‑based (LFU)
.maximumWeight(100) // weight‑based (mutually exclusive with size)
.expireAfterWrite(1, TimeUnit.SECONDS) // time‑based
.expireAfterAccess(1, TimeUnit.SECONDS) // time‑based
.removalListener((k, v, cause) -> log.info("Evicted {}", k));4. Refresh Mechanism
refreshAfterWrite()triggers a refresh after a specified interval, but only for LoadingCache or AsyncLoadingCache. The refresh occurs on the next read after the interval.
private static int NUM = 0;
LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.SECONDS)
.build(k -> ++NUM);
System.out.println(cache.get(1)); // 1
Thread.sleep(2000);
System.out.println(cache.getIfPresent(1)); // still 1
System.out.println(cache.getIfPresent(1)); // now 2 after refresh5. Statistics
Enable recordStats() to collect hit/miss counts, load times, eviction counts, etc.
LoadingCache<String, String> cache = Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.SECONDS)
.expireAfterWrite(1, TimeUnit.SECONDS)
.expireAfterAccess(1, TimeUnit.SECONDS)
.maximumSize(10)
.recordStats()
.build(key -> { Thread.sleep(1000); return new Date().toString(); });
cache.put("1", "shawn");
cache.get("1");
System.out.println(cache.stats());6. Spring Boot Integration
6.1 Required Annotations and Dependencies
Add @EnableCaching to a configuration class and include spring-boot-starter-cache and the Caffeine dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>6.2 Cache Constants
public class CacheConstants {
public static final int DEFAULT_EXPIRES = 3 * 60;
public static final int EXPIRES_5_MIN = 5 * 60;
public static final int EXPIRES_10_MIN = 10 * 60;
public static final String GET_USER = "GET:USER";
public static final String GET_DYNAMIC = "GET:DYNAMIC";
}6.3 Cache Configuration
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager manager = new SimpleCacheManager();
List<CaffeineCache> caches = new ArrayList<>();
for (CacheEnum e : CacheEnum.values()) {
caches.add(new CaffeineCache(e.getName(),
Caffeine.newBuilder()
.initialCapacity(50)
.maximumSize(1000)
.expireAfterAccess(e.getExpires(), TimeUnit.SECONDS)
.build()));
}
manager.setCaches(caches);
return manager;
}
}6.4 Using Cache Annotations
Typical annotations include @Cacheable, @CachePut, @CacheEvict, and @Caching. Example:
@Cacheable(value = CacheConstants.GET_USER, key = "'user'+#userId", sync = true)
@CacheEvict
public UserEntity getUserByUserId(Integer userId) {
UserEntity user = userMapper.findById(userId);
System.out.println("Queried DB");
return user;
}7. Summary
Caffeine provides a flexible, high‑performance local cache for Java applications. By combining size, weight, and time‑based eviction with optional refresh and statistics, developers can fine‑tune caching behavior. Spring Boot integration is straightforward through standard caching annotations and a simple CacheManager bean.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
