CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-ben-manes-caffeine--caffeine

A high performance caching library for Java providing Google Guava-inspired API with advanced eviction policies and comprehensive features

Pending
Overview
Eval results
Files

cache-policies.mddocs/

Cache Policies

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.

Policy Interface

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();
}

Accessing Cache Policies

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();

Eviction Policy

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);
}

Size-Based Eviction

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);

Weight-Based Eviction

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

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);
}

Fixed Expiration Policies

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);

Variable Expiration Policy

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");

Refresh Policy

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());

Policy Inspection Examples

Complete Policy Inspection

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());

Dynamic Policy Adjustment

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 minute

Policy Limitations

  • Async Cache Policies: Async caches return policies that operate on the underlying synchronous cache
  • Configuration Constraints: Some policy modifications may be rejected if they violate cache constraints
  • Thread Safety: Policy operations are thread-safe but modifications should be done carefully in concurrent environments
  • Performance Impact: Frequent policy modifications may impact cache performance

Install with Tessl CLI

npx tessl i tessl/maven-com-github-ben-manes-caffeine--caffeine

docs

asynchronous-caching.md

cache-construction.md

cache-policies.md

functional-interfaces.md

index.md

statistics.md

synchronous-caching.md

tile.json