CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-google-guava--guava

Comprehensive Java library providing essential utilities, immutable collections, caching, and concurrency tools for modern Java development.

Pending
Overview
Eval results
Files

caching.mddocs/

Caching

Flexible in-memory caching with automatic loading, expiration, eviction, and comprehensive statistics. Guava's caching utilities provide a powerful and efficient way to store frequently accessed data.

Package: com.google.common.cache

Cache Basics

Simple cache for storing key-value pairs with manual population.

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.TimeUnit;

// Basic cache creation
Cache<String, String> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build();

// Manual cache operations
cache.put("key", "value");
String value = cache.getIfPresent("key"); // Returns null if not present
cache.invalidate("key"); // Remove specific entry
cache.invalidateAll(); // Remove all entries

// Bulk operations
Map<String, String> entries = new HashMap<>();
entries.put("key1", "value1");
entries.put("key2", "value2");
cache.putAll(entries);

// Get all present values
Set<String> keys = ImmutableSet.of("key1", "key2", "missing");
Map<String, String> present = cache.getAllPresent(keys); // Only returns existing entries

// Cache statistics
CacheStats stats = cache.stats();
long hits = stats.hitCount();
long misses = stats.missCount();
double hitRate = stats.hitRate();

// Convert to ConcurrentMap view
ConcurrentMap<String, String> mapView = cache.asMap();

LoadingCache

Cache that automatically loads values when they're not present using a CacheLoader.

import com.google.common.cache.LoadingCache;
import com.google.common.cache.CacheLoader;

// Cache with automatic loading
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .expireAfterAccess(30, TimeUnit.MINUTES)
    .build(new CacheLoader<String, String>() {
        @Override
        public String load(String key) throws Exception {
            return loadFromDatabase(key); // Your loading logic
        }
        
        @Override  
        public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
            return loadMultipleFromDatabase(keys); // Optional bulk loading
        }
    });

// Automatic loading - loads if not present
String value = cache.get("key"); // Loads automatically, throws ExecutionException on load failure
String unchecked = cache.getUnchecked("key"); // Throws unchecked exception on load failure

// Bulk loading
ImmutableList<String> keys = ImmutableList.of("key1", "key2", "key3");
ImmutableMap<String, String> values = cache.getAll(keys); // Uses loadAll if available

// Refresh (asynchronous reload)
cache.refresh("key"); // Triggers background reload, returns stale value immediately

// Manual override
cache.put("key", "manual-value"); // Override loaded value

CacheBuilder Configuration

Comprehensive configuration options for cache behavior.

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.cache.Weigher;

// Size-based eviction
Cache<String, String> sizeCache = CacheBuilder.newBuilder()
    .maximumSize(1000) // Maximum number of entries
    .build();

// Weight-based eviction
Cache<String, List<String>> weightCache = CacheBuilder.newBuilder()
    .maximumWeight(100000)
    .weigher(new Weigher<String, List<String>>() {
        @Override
        public int weigh(String key, List<String> value) {
            return key.length() + value.size() * 10; // Custom weight calculation
        }
    })
    .build();

// Time-based expiration
Cache<String, String> timeCache = CacheBuilder.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES) // Expire after write
    .expireAfterAccess(5, TimeUnit.MINUTES) // Expire after last access
    .refreshAfterWrite(1, TimeUnit.MINUTES) // Refresh after write (for LoadingCache)
    .build();

// Reference-based eviction (memory-sensitive)
Cache<String, String> refCache = CacheBuilder.newBuilder()
    .weakKeys() // Weak references for keys
    .weakValues() // Weak references for values  
    .softValues() // Soft references for values (alternative to weak)
    .build();

// Statistics collection
Cache<String, String> statsCache = CacheBuilder.newBuilder()
    .recordStats() // Enable statistics collection
    .build();

// Removal listener
Cache<String, String> listenedCache = CacheBuilder.newBuilder()
    .removalListener(new RemovalListener<String, String>() {
        @Override
        public void onRemoval(RemovalNotification<String, String> notification) {
            String key = notification.getKey();
            String value = notification.getValue();
            RemovalCause cause = notification.getCause();
            System.out.println("Removed: " + key + " -> " + value + " (" + cause + ")");
        }
    })
    .build();

// Combined configuration
Cache<String, ExpensiveObject> productionCache = CacheBuilder.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(30, TimeUnit.MINUTES)
    .expireAfterAccess(10, TimeUnit.MINUTES)  
    .refreshAfterWrite(5, TimeUnit.MINUTES)
    .recordStats()
    .removalListener(loggingRemovalListener)
    .build();

Advanced CacheLoader Patterns

Sophisticated loading strategies for different use cases.

import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;

// Asynchronous loading
CacheLoader<String, String> asyncLoader = new CacheLoader<String, String>() {
    private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
        Executors.newFixedThreadPool(10));
    
    @Override
    public String load(String key) throws Exception {
        return loadSynchronously(key);
    }
    
    @Override
    public ListenableFuture<String> reload(String key, String oldValue) {
        return executor.submit(() -> {
            try {
                return loadSynchronously(key);
            } catch (Exception e) {
                return oldValue; // Return stale value on error
            }
        });
    }
    
    @Override
    public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
        // Efficient bulk loading
        return batchLoadFromDatabase(keys);
    }
};

// Fallback loading with error handling
CacheLoader<String, String> fallbackLoader = new CacheLoader<String, String>() {
    @Override
    public String load(String key) throws Exception {
        try {
            return primaryDataSource.load(key);
        } catch (Exception e) {
            // Fallback to secondary source
            String fallback = secondaryDataSource.load(key);
            if (fallback != null) {
                return fallback;
            }
            throw e; // Re-throw if all sources fail
        }
    }
};

// Conditional loading based on key
CacheLoader<String, Object> conditionalLoader = new CacheLoader<String, Object>() {
    @Override
    public Object load(String key) throws Exception {
        if (key.startsWith("user:")) {
            return userService.loadUser(key.substring(5));
        } else if (key.startsWith("config:")) {
            return configService.loadConfig(key.substring(7));
        } else {
            throw new IllegalArgumentException("Unknown key type: " + key);
        }
    }
};

Cache Statistics and Monitoring

Comprehensive statistics for cache performance monitoring.

import com.google.common.cache.CacheStats;

// Enable statistics collection
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
    .recordStats()
    .maximumSize(1000)
    .build(cacheLoader);

// Access statistics
CacheStats stats = cache.stats();

// Hit/Miss statistics  
long requestCount = stats.requestCount(); // Total requests
long hitCount = stats.hitCount(); // Cache hits
long missCount = stats.missCount(); // Cache misses  
double hitRate = stats.hitRate(); // Hit rate (0.0 to 1.0)
double missRate = stats.missRate(); // Miss rate (0.0 to 1.0)

// Load statistics
long loadCount = stats.loadCount(); // Number of loads
double averageLoadPenalty = stats.averageLoadPenalty(); // Average load time in nanoseconds
long totalLoadTime = stats.totalLoadTime(); // Total time spent loading

// Eviction statistics
long evictionCount = stats.evictionCount(); // Number of evictions

// Error statistics (for LoadingCache)
long loadExceptionCount = stats.loadExceptionCount(); // Failed loads
double loadExceptionRate = stats.loadExceptionRate(); // Load failure rate

// Monitoring cache performance
public void monitorCache(LoadingCache<?, ?> cache) {
    CacheStats stats = cache.stats();
    
    if (stats.hitRate() < 0.8) {
        System.out.println("Warning: Low hit rate - " + stats.hitRate());
    }
    
    if (stats.averageLoadPenalty() > TimeUnit.SECONDS.toNanos(1)) {
        System.out.println("Warning: Slow loading - " + stats.averageLoadPenalty() + "ns");
    }
    
    if (stats.evictionCount() > stats.requestCount() * 0.1) {
        System.out.println("Warning: High eviction rate");
    }
}

Removal Listeners and Cleanup

Handling cache entry removal for resource cleanup and monitoring.

import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.cache.RemovalCause;

// Cleanup removal listener
RemovalListener<String, DatabaseConnection> cleanupListener = 
    new RemovalListener<String, DatabaseConnection>() {
        @Override
        public void onRemoval(RemovalNotification<String, DatabaseConnection> notification) {
            DatabaseConnection connection = notification.getValue();
            if (connection != null) {
                try {
                    connection.close(); // Cleanup resources
                } catch (Exception e) {
                    logger.warn("Failed to close connection for key: " + notification.getKey(), e);
                }
            }
        }
    };

// Monitoring removal listener  
RemovalListener<String, String> monitoringListener = 
    new RemovalListener<String, String>() {
        @Override
        public void onRemoval(RemovalNotification<String, String> notification) {
            RemovalCause cause = notification.getCause();
            
            switch (cause) {
                case EXPLICIT:
                    // Manual removal via invalidate()
                    break;
                case REPLACED:
                    // Value replaced with put()
                    break;
                case COLLECTED:
                    // Garbage collected (weak/soft references)
                    break;
                case EXPIRED:
                    // Expired based on time
                    break;
                case SIZE:
                    // Evicted due to size constraints
                    metrics.incrementEvictionCounter();
                    break;
            }
            
            logger.debug("Cache entry removed: {} -> {} ({})", 
                notification.getKey(), notification.getValue(), cause);
        }
    };

// Asynchronous removal listener (for expensive cleanup)
RemovalListener<String, String> asyncListener = RemovalListeners.asynchronous(
    expensiveCleanupListener, 
    Executors.newSingleThreadExecutor()
);

Cache Patterns and Best Practices

Common patterns for effective cache usage.

// Null value handling
LoadingCache<String, Optional<String>> nullSafeCache = CacheBuilder.newBuilder()
    .build(new CacheLoader<String, Optional<String>>() {
        @Override
        public Optional<String> load(String key) throws Exception {
            String value = database.get(key);
            return Optional.fromNullable(value); // Wrap nulls in Optional
        }
    });

// Usage with null handling
Optional<String> result = nullSafeCache.get("key");
if (result.isPresent()) {
    String value = result.get();
    // Use value
}

// Refresh-ahead pattern
LoadingCache<String, String> refreshCache = CacheBuilder.newBuilder()
    .refreshAfterWrite(5, TimeUnit.MINUTES) // Refresh after 5 minutes
    .expireAfterWrite(10, TimeUnit.MINUTES) // Expire after 10 minutes
    .build(new CacheLoader<String, String>() {
        @Override
        public String load(String key) throws Exception {
            return expensiveComputation(key);
        }
        
        @Override
        public ListenableFuture<String> reload(String key, String oldValue) {
            // Asynchronous reload - returns old value immediately while loading new
            return backgroundExecutor.submit(() -> expensiveComputation(key));
        }
    });

// Write-through cache pattern
public class WritethroughCache {
    private final LoadingCache<String, String> cache;
    private final Database database;
    
    public Writethrough Cache(Database database) {
        this.database = database;
        this.cache = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return database.get(key);
                }
            });
    }
    
    public String get(String key) throws ExecutionException {
        return cache.get(key);
    }
    
    public void put(String key, String value) {
        database.put(key, value); // Write to database first
        cache.put(key, value); // Then update cache
    }
    
    public void remove(String key) {
        database.remove(key); // Remove from database first
        cache.invalidate(key); // Then remove from cache
    }
}

// Multi-level cache
public class MultiLevelCache {
    private final Cache<String, String> l1Cache; // Small, fast cache
    private final LoadingCache<String, String> l2Cache; // Larger cache
    
    public MultiLevelCache() {
        this.l1Cache = CacheBuilder.newBuilder()
            .maximumSize(100)
            .expireAfterAccess(1, TimeUnit.MINUTES)
            .build();
            
        this.l2Cache = CacheBuilder.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return database.get(key);
                }
            });
    }
    
    public String get(String key) throws ExecutionException {
        // Try L1 cache first
        String value = l1Cache.getIfPresent(key);
        if (value != null) {
            return value;
        }
        
        // Fall back to L2 cache
        value = l2Cache.get(key);
        l1Cache.put(key, value); // Promote to L1
        return value;
    }
}

Cache Specification String

Configure caches using string specifications for external configuration.

import com.google.common.cache.CacheBuilderSpec;

// Parse cache specification from string (useful for configuration files)
String spec = "maximumSize=1000,expireAfterWrite=30m,recordStats";
CacheBuilderSpec builderSpec = CacheBuilderSpec.parse(spec);
LoadingCache<String, String> cache = CacheBuilder.from(builderSpec)
    .build(cacheLoader);

// Alternative: direct parsing
LoadingCache<String, String> cache2 = CacheBuilder.from("maximumSize=500,expireAfterAccess=10m")
    .recordStats()
    .build(cacheLoader);

// Specification format examples:
// "maximumSize=1000" - size limit
// "maximumWeight=50000" - weight limit  
// "expireAfterWrite=30m" - expire 30 minutes after write
// "expireAfterAccess=1h" - expire 1 hour after access
// "refreshAfterWrite=5m" - refresh 5 minutes after write
// "weakKeys" - use weak references for keys
// "weakValues" - use weak references for values
// "softValues" - use soft references for values
// "recordStats" - enable statistics

Testing Cache Behavior

Utilities and patterns for testing cache implementations.

import com.google.common.testing.FakeTicker;
import java.util.concurrent.TimeUnit;

// Testing time-based expiration
public void testCacheExpiration() {
    FakeTicker ticker = new FakeTicker();
    
    LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .ticker(ticker) // Use fake ticker for testing
        .build(cacheLoader);
    
    // Add entry
    cache.put("key", "value");
    assertEquals("value", cache.getIfPresent("key"));
    
    // Advance time by 5 minutes
    ticker.advance(5, TimeUnit.MINUTES);
    assertEquals("value", cache.getIfPresent("key")); // Still present
    
    // Advance time by another 6 minutes (total 11 minutes)
    ticker.advance(6, TimeUnit.MINUTES);
    assertNull(cache.getIfPresent("key")); // Should be expired
}

// Testing cache statistics
public void testCacheStats() {
    LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(cacheLoader);
    
    // Trigger cache operations
    cache.get("key1"); // Miss + Load
    cache.get("key1"); // Hit
    cache.get("key2"); // Miss + Load
    
    CacheStats stats = cache.stats();
    assertEquals(3, stats.requestCount());
    assertEquals(1, stats.hitCount());
    assertEquals(2, stats.missCount());
    assertEquals(2, stats.loadCount());
}

Guava's caching framework provides a robust, flexible solution for improving application performance through intelligent data caching with automatic loading, configurable expiration strategies, and comprehensive monitoring capabilities.

Install with Tessl CLI

npx tessl i tessl/maven-com-google-guava--guava

docs

basic-utilities.md

caching.md

collections.md

concurrency.md

graph-api.md

hash-math.md

immutable-collections.md

index.md

io-utilities.md

other-utilities.md

tile.json