Comprehensive Java library providing essential utilities, immutable collections, caching, and concurrency tools for modern Java development.
—
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.
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();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 valueComprehensive 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();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);
}
}
};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");
}
}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()
);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;
}
}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 statisticsUtilities 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