A high performance caching library for Java providing Google Guava-inspired API with advanced eviction policies and comprehensive features
—
The Policy interface provides runtime access to cache configuration and behavior, allowing inspection and modification of cache policies after creation. This enables dynamic tuning and monitoring of cache performance characteristics.
public interface Policy<K, V> {
// Statistics inspection
boolean isRecordingStats();
// Entry inspection (no side effects)
V getIfPresentQuietly(K key);
CacheEntry<K, V> getEntryIfPresentQuietly(K key);
// Active refreshes
Map<K, CompletableFuture<V>> refreshes();
// Policy access
Optional<Eviction<K, V>> eviction();
Optional<FixedExpiration<K, V>> expireAfterAccess();
Optional<FixedExpiration<K, V>> expireAfterWrite();
Optional<VarExpiration<K, V>> expireVariably();
Optional<FixedRefresh<K, V>> refreshAfterWrite();
}
// Cache entry metadata interface
interface CacheEntry<K, V> extends Map.Entry<K, V> {
int weight();
long expiresAt();
Duration expiresAfter();
long refreshableAt();
Duration refreshableAfter();
long snapshotAt();
}Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(Duration.ofMinutes(30))
.expireAfterWrite(Duration.ofHours(1))
.refreshAfterWrite(Duration.ofMinutes(10))
.recordStats()
.build();
Policy<String, String> policy = cache.policy();
// Check if statistics are enabled
boolean recordingStats = policy.isRecordingStats(); // true
// Access available policies
Optional<Policy.Eviction<String, String>> eviction = policy.eviction();
Optional<Policy.Expiration<String, String>> accessExpiration = policy.expireAfterAccess();
Optional<Policy.Expiration<String, String>> writeExpiration = policy.expireAfterWrite();
Optional<Policy.Expiration<String, String>> refreshPolicy = policy.refreshAfterWrite();The eviction policy controls size-based and weight-based cache limits.
interface Eviction<K, V> {
// Size inspection and modification
boolean isWeighted();
OptionalInt weightOf(K key);
OptionalLong weightedSize();
long getMaximum();
void setMaximum(long maximum);
// Entry inspection by count
Map<K, V> coldest(int limit);
Map<K, V> hottest(int limit);
// Entry inspection by weight (v3.0.4+)
Map<K, V> coldestWeighted(long weightLimit);
Map<K, V> hottestWeighted(long weightLimit);
// Stream-based entry inspection (v3.0.6+)
<T> T coldest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
<T> T hottest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
}Cache<String, String> sizeCache = Caffeine.newBuilder()
.maximumSize(1000)
.build();
Policy.Eviction<String, String> evictionPolicy = sizeCache.policy().eviction().get();
// Inspect current configuration
boolean isWeighted = evictionPolicy.isWeighted(); // false for size-based
long maxSize = evictionPolicy.getMaximum().getAsLong(); // 1000
// Dynamically adjust maximum size
evictionPolicy.setMaximum(2000);
long newMaxSize = evictionPolicy.getMaximum().getAsLong(); // 2000
// Inspect hottest and coldest entries
sizeCache.put("hot1", "value1");
sizeCache.put("hot2", "value2");
sizeCache.put("cold1", "value1");
sizeCache.put("cold2", "value2");
// Access patterns affect hotness
sizeCache.getIfPresent("hot1");
sizeCache.getIfPresent("hot2");
Map<String, String> hottestEntries = evictionPolicy.hottest(2);
Map<String, String> coldestEntries = evictionPolicy.coldest(2);Cache<String, String> weightCache = Caffeine.newBuilder()
.maximumWeight(10000)
.weigher((key, value) -> key.length() + value.length())
.build();
Policy.Eviction<String, String> weightEvictionPolicy = weightCache.policy().eviction().get();
// Weight-specific operations
boolean isWeighted = weightEvictionPolicy.isWeighted(); // true
long maxWeight = weightEvictionPolicy.getMaximum().getAsLong(); // 10000
long currentWeight = weightEvictionPolicy.weightedSize().getAsLong();
// Adjust maximum weight dynamically
weightEvictionPolicy.setMaximum(20000);
System.out.println("Current weight: " + currentWeight + "/" + maxWeight);Expiration policies control time-based entry removal and provide runtime inspection.
interface FixedExpiration<K, V> {
// Duration inspection and modification
OptionalLong ageOf(K key, TimeUnit unit);
Duration ageOf(K key);
long getExpiresAfter(TimeUnit unit);
Duration getExpiresAfter();
void setExpiresAfter(long duration, TimeUnit unit);
void setExpiresAfter(Duration duration);
// Entry inspection by count
Map<K, V> oldest(int limit);
Map<K, V> youngest(int limit);
// Stream-based entry inspection (v3.0.6+)
<T> T oldest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
<T> T youngest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
}
interface VarExpiration<K, V> {
// Variable expiration inspection and modification
OptionalLong getExpiresAfter(K key, TimeUnit unit);
Optional<Duration> getExpiresAfter(K key);
void setExpiresAfter(K key, long duration, TimeUnit unit);
void setExpiresAfter(K key, Duration duration);
// Entry modification with custom expiration
V putIfAbsent(K key, V value, long duration, TimeUnit unit);
V putIfAbsent(K key, V value, Duration duration);
V put(K key, V value, long duration, TimeUnit unit);
V put(K key, V value, Duration duration);
V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction, Duration duration);
// Age and entry inspection
OptionalLong ageOf(K key, TimeUnit unit);
Map<K, V> oldest(int limit);
Map<K, V> youngest(int limit);
// Stream-based entry inspection (v3.0.6+)
<T> T oldest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
<T> T youngest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
}
interface FixedRefresh<K, V> {
OptionalLong ageOf(K key, TimeUnit unit);
Duration ageOf(K key);
long getRefreshesAfter(TimeUnit unit);
Duration getRefreshesAfter();
void setRefreshesAfter(long duration, TimeUnit unit);
void setRefreshesAfter(Duration duration);
}Cache<String, String> expiringCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(Duration.ofMinutes(30))
.expireAfterWrite(Duration.ofHours(2))
.build();
// Access expiration policy
Policy.Expiration<String, String> accessExpiration =
expiringCache.policy().expireAfterAccess().get();
// Inspect current expiration settings
Duration accessDuration = accessExpiration.getExpiresAfter().get(); // 30 minutes
// Modify expiration duration at runtime
accessExpiration.setExpiresAfter(Duration.ofMinutes(45));
// Write expiration policy
Policy.Expiration<String, String> writeExpiration =
expiringCache.policy().expireAfterWrite().get();
Duration writeDuration = writeExpiration.getExpiresAfter().get(); // 2 hours
writeExpiration.setExpiresAfter(Duration.ofHours(3));
// Inspect entry ages
expiringCache.put("test_key", "test_value");
Thread.sleep(1000);
OptionalLong ageInSeconds = accessExpiration.ageOf("test_key", TimeUnit.SECONDS);
if (ageInSeconds.isPresent()) {
System.out.println("Entry age: " + ageInSeconds.getAsLong() + " seconds");
}
// Get oldest and youngest entries
Map<String, String> oldestEntries = accessExpiration.oldest(5);
Map<String, String> youngestEntries = accessExpiration.youngest(5);Cache<String, String> varExpiringCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfter(Expiry.creating((key, value) -> {
// Different expiration based on key prefix
if (key.startsWith("temp_")) {
return Duration.ofMinutes(5);
} else if (key.startsWith("cache_")) {
return Duration.ofHours(1);
} else {
return Duration.ofMinutes(30);
}
}))
.build();
Policy.VarExpiration<String, String> varExpiration =
varExpiringCache.policy().expireVariably().get();
// Add entries with different expiration times
varExpiringCache.put("temp_data", "temporary");
varExpiringCache.put("cache_data", "cached");
varExpiringCache.put("normal_data", "normal");
// Inspect individual entry expiration
OptionalLong tempExpiration = varExpiration.getExpiresAfter("temp_data", TimeUnit.MINUTES);
OptionalLong cacheExpiration = varExpiration.getExpiresAfter("cache_data", TimeUnit.MINUTES);
System.out.println("Temp expires in: " + tempExpiration.orElse(-1) + " minutes");
System.out.println("Cache expires in: " + cacheExpiration.orElse(-1) + " minutes");
// Modify expiration for specific entries
varExpiration.setExpiresAfter("temp_data", 10, TimeUnit.MINUTES);
// Inspect ages
OptionalLong tempAge = varExpiration.ageOf("temp_data", TimeUnit.SECONDS);
System.out.println("Temp entry age: " + tempAge.orElse(-1) + " seconds");The refresh policy controls automatic background refresh behavior for loading caches.
LoadingCache<String, String> refreshingCache = Caffeine.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(Duration.ofMinutes(5))
.build(key -> "loaded_" + System.currentTimeMillis());
Policy.Expiration<String, String> refreshPolicy =
refreshingCache.policy().refreshAfterWrite().get();
// Inspect refresh configuration
Duration refreshInterval = refreshPolicy.getExpiresAfter().get(); // 5 minutes
// Modify refresh interval
refreshPolicy.setExpiresAfter(Duration.ofMinutes(10));
// Check time since last refresh
refreshingCache.get("test_key");
Thread.sleep(1000);
OptionalLong timeSinceRefresh = refreshPolicy.ageOf("test_key", TimeUnit.SECONDS);
System.out.println("Time since last refresh: " + timeSinceRefresh.orElse(-1) + " seconds");
// Get entries that need refresh soon
Map<String, String> oldestEntries = refreshPolicy.oldest(10);
System.out.println("Entries needing refresh soon: " + oldestEntries.keySet());Cache<String, String> fullyConfiguredCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterAccess(Duration.ofMinutes(30))
.expireAfterWrite(Duration.ofHours(2))
.recordStats()
.build();
Policy<String, String> policy = fullyConfiguredCache.policy();
// Check all available policies
System.out.println("Recording stats: " + policy.isRecordingStats());
if (policy.eviction().isPresent()) {
Policy.Eviction<String, String> eviction = policy.eviction().get();
System.out.println("Max size: " + eviction.getMaximum().orElse(-1));
System.out.println("Is weighted: " + eviction.isWeighted());
}
if (policy.expireAfterAccess().isPresent()) {
Policy.Expiration<String, String> accessExp = policy.expireAfterAccess().get();
System.out.println("Expire after access: " + accessExp.getExpiresAfter().orElse(null));
}
if (policy.expireAfterWrite().isPresent()) {
Policy.Expiration<String, String> writeExp = policy.expireAfterWrite().get();
System.out.println("Expire after write: " + writeExp.getExpiresAfter().orElse(null));
}
// Variable expiration and refresh would be empty for this cache
System.out.println("Has variable expiration: " + policy.expireVariably().isPresent());
System.out.println("Has refresh policy: " + policy.refreshAfterWrite().isPresent());Cache<String, String> adaptiveCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(Duration.ofMinutes(10))
.build();
Policy<String, String> policy = adaptiveCache.policy();
// Monitor cache performance and adjust policies
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
CacheStats stats = adaptiveCache.stats();
double hitRate = stats.hitRate();
// Adjust expiration based on hit rate
if (hitRate < 0.8 && policy.expireAfterAccess().isPresent()) {
// Low hit rate - increase expiration time
Duration current = policy.expireAfterAccess().get().getExpiresAfter().get();
Duration increased = current.multipliedBy(2);
policy.expireAfterAccess().get().setExpiresAfter(increased);
System.out.println("Increased expiration to: " + increased);
}
// Adjust size based on eviction rate
if (stats.evictionCount() > 100 && policy.eviction().isPresent()) {
// High eviction rate - increase cache size
long currentMax = policy.eviction().get().getMaximum().getAsLong();
policy.eviction().get().setMaximum(currentMax * 2);
System.out.println("Increased max size to: " + (currentMax * 2));
}
}
}, 60000, 60000); // Check every minuteInstall with Tessl CLI
npx tessl i tessl/maven-com-github-ben-manes-caffeine--caffeine