Guava Cache

Java cacheGoogle librarylocal cacheCacheBuilderstability

GitHub Overview

google/guava

Google core libraries for Java

Stars51,221
Watchers2,354
Forks11,085
Created:May 29, 2014
Language:Java
License:Apache License 2.0

Topics

guavajava

Star History

google/guava Star History
Data as of: 10/22/2025, 10:05 AM

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