Guava Cache
GitHub Overview
Topics
Star History
Library
Guava Cache
Overview
Guava Cache is a local cache library for Java developed by Google. Provided as part of Google Core Libraries for Java, it enables cache construction through a simple and intuitive CacheBuilder pattern. As of 2025, while inferior to Caffeine in terms of performance, it continues to be used in many legacy projects due to its track record and stability from years of use within Google. Although Caffeine is recommended for new projects, it occupies an important position as a stable cache solution for existing projects with Guava dependencies.
Details
Guava Cache 33.4.8 is the latest version as of 2025, supporting operation on Java 11+. Through years of use within Google, it has achieved a refined API and high stability, though it has performance constraints due to its single-threaded design. Since the advent of Caffeine, new adoption has decreased, but it continues to be utilized in existing large-scale projects and serves as an option when there are dependencies on the entire Guava library ecosystem. It covers basic cache functionality including LoadingCache, refresh capabilities, and statistics collection, providing an API with low learning costs.
Key Features
- CacheBuilder Pattern: Intuitive and readable cache configuration
- Integrated Ecosystem: Integration with the entire Google Guava library
- LoadingCache: Automatic data loading functionality
- Statistics and Monitoring: Comprehensive cache statistics
- Long-term Stability: Proven track record of years of use within Google
- Documentation: Rich documentation and community support
Pros and Cons
Pros
- Proven reliability through years of stable use within Google
- Intuitive and understandable CacheBuilder pattern
- Consistency through integration with the entire Guava library
- Rich documentation and learning resources
- Minimal migration costs for existing projects
- Fewer unexpected behaviors due to mature API
Cons
- Significantly inferior performance compared to Caffeine (several times difference)
- Concurrency constraints due to single-threaded design
- Hit rate limitations due to LRU algorithm
- Low expectations for new feature additions or performance improvements
- No modern Window TinyLFU algorithm
- Insufficient support for asynchronous operations
Reference Pages
Code Examples
Adding Dependencies
// Gradle setup
implementation 'com.google.guava:guava:33.4.8-jre'
// For Android
implementation 'com.google.guava:guava:33.4.8-android'
<!-- Maven setup -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.8-jre</version>
</dependency>
Basic Cache Creation and Operations
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.TimeUnit;
public class BasicGuavaCacheExample {
public static void main(String[] args) {
// Basic cache creation
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1000) // Maximum size
.expireAfterWrite(5, TimeUnit.MINUTES) // Expire 5 minutes after write
.expireAfterAccess(2, TimeUnit.MINUTES) // Expire 2 minutes after access
.recordStats() // Enable statistics recording
.build();
// Cache operations
cache.put("user:1", "John Doe");
String user = cache.getIfPresent("user:1");
System.out.println("User: " + user); // "User: John Doe"
// Default value for missing keys
String defaultUser = cache.get("user:999", () -> "Unknown User");
System.out.println("Default: " + defaultUser); // "Default: Unknown User"
// Bulk operations
Map<String, String> users = Map.of(
"user:2", "Jane Smith",
"user:3", "Bob Johnson"
);
cache.putAll(users);
// Get statistics
CacheStats stats = cache.stats();
System.out.printf("Hit rate: %.2f%%\n", stats.hitRate() * 100);
System.out.printf("Miss count: %d\n", stats.missCount());
System.out.printf("Eviction count: %d\n", stats.evictionCount());
// Cache size and invalidation
System.out.println("Cache size: " + cache.size());
cache.invalidate("user:1"); // Individual removal
cache.invalidateAll(); // Clear all
}
}
LoadingCache (Automatic Loading)
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
public class LoadingCacheExample {
// Database access simulation
private static String loadUserFromDatabase(String userId) {
try {
Thread.sleep(100); // Simulate DB access time
return "User-" + userId + "-from-DB";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
// LoadingCache creation
LoadingCache<String, String> userCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.refreshAfterWrite(5, TimeUnit.MINUTES) // Background refresh after 5 minutes
.recordStats()
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return loadUserFromDatabase(key);
}
});
// Automatic loading
String user1 = userCache.get("user:123"); // Load from DB
String user2 = userCache.get("user:123"); // Get from cache
// Bulk loading
Map<String, String> users = userCache.getAll(
List.of("user:100", "user:101", "user:102")
);
// Manual refresh
userCache.refresh("user:123");
// Check statistics
CacheStats stats = userCache.stats();
System.out.printf("Hit rate: %.2f%%\n", stats.hitRate() * 100);
System.out.printf("Load count: %d\n", stats.loadCount());
System.out.printf("Average load time: %.2f ms\n",
stats.averageLoadTime() / 1_000_000.0);
}
}
Advanced Cache Configuration and Customization
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.cache.Weigher;
public class AdvancedGuavaCacheExample {
public static void main(String[] args) {
// Custom RemovalListener
RemovalListener<String, String> removalListener =
new RemovalListener<String, String>() {
@Override
public void onRemoval(RemovalNotification<String, String> notification) {
System.out.printf("Removed: %s=%s (cause: %s)\n",
notification.getKey(),
notification.getValue(),
notification.getCause());
// Cleanup processing
if (notification.getCause().wasEvicted()) {
cleanupExpiredData(notification.getKey(), notification.getValue());
}
}
};
// Weighted cache
LoadingCache<String, String> weightedCache = CacheBuilder.newBuilder()
.maximumWeight(100000) // Maximum weight
.weigher(new Weigher<String, String>() {
@Override
public int weigh(String key, String value) {
// Weight calculation based on object size
return key.length() + value.length();
}
})
.expireAfterWrite(1, TimeUnit.HOURS)
.expireAfterAccess(30, TimeUnit.MINUTES)
.refreshAfterWrite(15, TimeUnit.MINUTES)
.removalListener(removalListener)
.recordStats()
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return fetchDataFromSource(key);
}
});
// Weak reference cache
Cache<String, Object> weakCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.weakKeys() // Weak reference for keys
.weakValues() // Weak reference for values
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
// Soft reference cache (auto-removal under memory pressure)
Cache<String, Object> softCache = CacheBuilder.newBuilder()
.softValues() // Soft reference for values
.maximumSize(500)
.build();
}
private static void cleanupExpiredData(String key, String value) {
// Cleanup process for expired data
System.out.println("Cleaning up expired data: " + key);
}
private static String fetchDataFromSource(String key) {
// Data retrieval from source
return "Fresh data for " + key;
}
}
Spring Boot Integration
// Spring Boot Configuration
@Configuration
@EnableCaching
public class GuavaCacheConfiguration {
@Bean
public CacheManager cacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
cacheManager.setCacheBuilder(
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(Duration.ofMinutes(10))
.recordStats()
);
return cacheManager;
}
// Multiple cache configuration
@Bean
public CacheManager multipleCacheManager() {
GuavaCacheManager manager = new GuavaCacheManager();
// User cache
manager.setCacheNames(Arrays.asList("users", "sessions"));
manager.setCacheBuilder(
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofHours(1))
.recordStats()
);
return manager;
}
// Custom cache bean
@Bean
public LoadingCache<String, User> userCache() {
return CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.recordStats()
.build(new CacheLoader<String, User>() {
@Override
public User load(String userId) throws Exception {
return userService.findById(userId);
}
});
}
}
// Service class usage
@Service
public class UserService {
@Autowired
private LoadingCache<String, User> userCache;
@Cacheable(value = "users", key = "#userId")
public User getUserById(String userId) {
// Retrieve user from database
return userRepository.findById(userId);
}
@CacheEvict(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
// Direct LoadingCache usage
public User getUserWithLoadingCache(String userId) throws Exception {
return userCache.get(userId);
}
// Get statistics
public CacheStats getCacheStats() {
return userCache.stats();
}
}
Performance Monitoring and Optimization
public class GuavaCacheMonitoringExample {
private final LoadingCache<String, String> cache;
private final ScheduledExecutorService scheduler;
public GuavaCacheMonitoringExample() {
this.cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()
.removalListener(this::onRemoval)
.build(this::loadValue);
this.scheduler = Executors.newScheduledThreadPool(1);
setupMonitoring();
}
private void setupMonitoring() {
// Periodic statistics report
scheduler.scheduleAtFixedRate(() -> {
CacheStats stats = cache.stats();
System.out.println("=== Guava Cache Statistics ===");
System.out.printf("Hit Rate: %.2f%%\n", stats.hitRate() * 100);
System.out.printf("Miss Rate: %.2f%%\n", stats.missRate() * 100);
System.out.printf("Hit Count: %d\n", stats.hitCount());
System.out.printf("Miss Count: %d\n", stats.missCount());
System.out.printf("Load Count: %d\n", stats.loadCount());
System.out.printf("Eviction Count: %d\n", stats.evictionCount());
System.out.printf("Average Load Time: %.2f ms\n",
stats.averageLoadTime() / 1_000_000.0);
System.out.printf("Load Exception Count: %d\n", stats.loadExceptionCount());
System.out.println("===============================");
}, 0, 30, TimeUnit.SECONDS);
}
private void onRemoval(RemovalNotification<String, String> notification) {
System.out.printf("Removed: %s (cause: %s)\n",
notification.getKey(), notification.getCause());
}
private String loadValue(String key) {
// Simulated data loading
try {
Thread.sleep(50); // Simulate loading time
return "Loaded value for " + key;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
// Benchmark test
public void benchmarkCache() {
int iterations = 10000;
long startTime = System.nanoTime();
// Write benchmark
for (int i = 0; i < iterations; i++) {
cache.put("key" + i, "value" + i);
}
long writeTime = System.nanoTime() - startTime;
// Read benchmark
startTime = System.nanoTime();
for (int i = 0; i < iterations; i++) {
cache.getIfPresent("key" + (i % 1000));
}
long readTime = System.nanoTime() - startTime;
System.out.printf("Write time: %.2f ms\n", writeTime / 1_000_000.0);
System.out.printf("Read time: %.2f ms\n", readTime / 1_000_000.0);
System.out.printf("Write throughput: %.0f ops/sec\n",
iterations * 1_000_000_000.0 / writeTime);
System.out.printf("Read throughput: %.0f ops/sec\n",
iterations * 1_000_000_000.0 / readTime);
}
public void shutdown() {
scheduler.shutdown();
}
}
Custom CacheLoader and Error Handling
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
public class CustomCacheLoaderExample {
public static void main(String[] args) throws Exception {
// CacheLoader with error handling
LoadingCache<String, String> resilientCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.refreshAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return loadWithRetry(key, 3);
}
@Override
public ListenableFuture<String> reload(String key, String oldValue) {
// Asynchronous reload
ListenableFutureTask<String> task = ListenableFutureTask.create(() -> {
try {
return loadWithRetry(key, 3);
} catch (Exception e) {
// Return old value on error
System.err.println("Reload failed for " + key + ", using old value");
return oldValue;
}
});
// Execute in background
Executors.newSingleThreadExecutor().execute(task);
return task;
}
});
// Cache with timeout
LoadingCache<String, String> timeoutCache = CacheBuilder.newBuilder()
.maximumSize(500)
.expireAfterWrite(15, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return loadWithTimeout(key, 5000); // 5 second timeout
}
});
// Cache with fallback strategy
LoadingCache<String, String> fallbackCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
try {
return loadFromPrimarySource(key);
} catch (Exception primaryError) {
System.err.println("Primary source failed: " + primaryError.getMessage());
try {
return loadFromSecondarySource(key);
} catch (Exception secondaryError) {
System.err.println("Secondary source failed: " + secondaryError.getMessage());
return getDefaultValue(key);
}
}
}
});
}
private static String loadWithRetry(String key, int maxRetries) throws Exception {
Exception lastException = null;
for (int i = 0; i < maxRetries; i++) {
try {
return loadFromExternalService(key);
} catch (Exception e) {
lastException = e;
System.err.printf("Load attempt %d failed for key %s: %s\n",
i + 1, key, e.getMessage());
if (i < maxRetries - 1) {
// Exponential backoff
long delay = (long) Math.pow(2, i) * 100;
Thread.sleep(delay);
}
}
}
throw new Exception("Failed to load after " + maxRetries + " attempts", lastException);
}
private static String loadWithTimeout(String key, long timeoutMs) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<String> future = executor.submit(() -> loadFromExternalService(key));
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new Exception("Load timeout for key: " + key);
} finally {
executor.shutdown();
}
}
private static String loadFromExternalService(String key) throws Exception {
// Simulate data retrieval from external service
Thread.sleep(100);
// Simulate 20% error rate
if (Math.random() < 0.2) {
throw new Exception("External service error");
}
return "Data for " + key + " from external service";
}
private static String loadFromPrimarySource(String key) throws Exception {
// Data retrieval from primary source
return loadFromExternalService(key);
}
private static String loadFromSecondarySource(String key) throws Exception {
// Data retrieval from secondary source
Thread.sleep(50);
return "Fallback data for " + key;
}
private static String getDefaultValue(String key) {
// Get default value
return "Default value for " + key;
}
}
Downgrade Support from Caffeine to Guava
/**
* Migration support class from Caffeine to Guava Cache
* For maintaining compatibility in legacy systems
*/
public class CaffeineToGuavaMigration {
// Wrapper with Caffeine-like interface
public static class GuavaCompatibilityWrapper<K, V> {
private final LoadingCache<K, V> guavaCache;
public GuavaCompatibilityWrapper(LoadingCache<K, V> guavaCache) {
this.guavaCache = guavaCache;
}
// Caffeine-style API methods
public V get(K key) {
try {
return guavaCache.get(key);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public V getIfPresent(K key) {
return guavaCache.getIfPresent(key);
}
public void put(K key, V value) {
guavaCache.put(key, value);
}
public void invalidate(K key) {
guavaCache.invalidate(key);
}
public void invalidateAll() {
guavaCache.invalidateAll();
}
public long estimatedSize() {
return guavaCache.size();
}
public CacheStats stats() {
return guavaCache.stats();
}
}
// Factory method for Guava Cache builder
public static <K, V> GuavaCompatibilityWrapper<K, V> createCompatibleCache(
int maxSize,
Duration expireAfterWrite,
Function<K, V> loader) {
LoadingCache<K, V> cache = CacheBuilder.newBuilder()
.maximumSize(maxSize)
.expireAfterWrite(expireAfterWrite.toMillis(), TimeUnit.MILLISECONDS)
.recordStats()
.build(new CacheLoader<K, V>() {
@Override
public V load(K key) throws Exception {
return loader.apply(key);
}
});
return new GuavaCompatibilityWrapper<>(cache);
}
// Configuration conversion helper
public static CacheBuilder<Object, Object> convertCaffeineSpec(String caffeineSpec) {
// Convert Caffeine configuration string to Guava configuration
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
// Simple conversion logic (actual implementation would use regex, etc.)
if (caffeineSpec.contains("maximumSize=")) {
String sizeStr = extractValue(caffeineSpec, "maximumSize=");
builder.maximumSize(Long.parseLong(sizeStr));
}
if (caffeineSpec.contains("expireAfterWrite=")) {
String durationStr = extractValue(caffeineSpec, "expireAfterWrite=");
long duration = parseDuration(durationStr);
builder.expireAfterWrite(duration, TimeUnit.MILLISECONDS);
}
if (caffeineSpec.contains("recordStats")) {
builder.recordStats();
}
return builder;
}
private static String extractValue(String spec, String key) {
int start = spec.indexOf(key) + key.length();
int end = spec.indexOf(',', start);
if (end == -1) end = spec.length();
return spec.substring(start, end);
}
private static long parseDuration(String duration) {
// Simple Duration parsing
if (duration.endsWith("m")) {
return Long.parseLong(duration.substring(0, duration.length() - 1)) * 60 * 1000;
} else if (duration.endsWith("s")) {
return Long.parseLong(duration.substring(0, duration.length() - 1)) * 1000;
}
return Long.parseLong(duration);
}
}