0
# Caching
1
2
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.
3
4
## Package: com.google.common.cache
5
6
### Cache Basics
7
8
Simple cache for storing key-value pairs with manual population.
9
10
```java { .api }
11
import com.google.common.cache.Cache;
12
import com.google.common.cache.CacheBuilder;
13
import java.util.concurrent.TimeUnit;
14
15
// Basic cache creation
16
Cache<String, String> cache = CacheBuilder.newBuilder()
17
.maximumSize(1000)
18
.expireAfterWrite(10, TimeUnit.MINUTES)
19
.build();
20
21
// Manual cache operations
22
cache.put("key", "value");
23
String value = cache.getIfPresent("key"); // Returns null if not present
24
cache.invalidate("key"); // Remove specific entry
25
cache.invalidateAll(); // Remove all entries
26
27
// Bulk operations
28
Map<String, String> entries = new HashMap<>();
29
entries.put("key1", "value1");
30
entries.put("key2", "value2");
31
cache.putAll(entries);
32
33
// Get all present values
34
Set<String> keys = ImmutableSet.of("key1", "key2", "missing");
35
Map<String, String> present = cache.getAllPresent(keys); // Only returns existing entries
36
37
// Cache statistics
38
CacheStats stats = cache.stats();
39
long hits = stats.hitCount();
40
long misses = stats.missCount();
41
double hitRate = stats.hitRate();
42
43
// Convert to ConcurrentMap view
44
ConcurrentMap<String, String> mapView = cache.asMap();
45
```
46
47
### LoadingCache
48
49
Cache that automatically loads values when they're not present using a CacheLoader.
50
51
```java { .api }
52
import com.google.common.cache.LoadingCache;
53
import com.google.common.cache.CacheLoader;
54
55
// Cache with automatic loading
56
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
57
.maximumSize(1000)
58
.expireAfterAccess(30, TimeUnit.MINUTES)
59
.build(new CacheLoader<String, String>() {
60
@Override
61
public String load(String key) throws Exception {
62
return loadFromDatabase(key); // Your loading logic
63
}
64
65
@Override
66
public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
67
return loadMultipleFromDatabase(keys); // Optional bulk loading
68
}
69
});
70
71
// Automatic loading - loads if not present
72
String value = cache.get("key"); // Loads automatically, throws ExecutionException on load failure
73
String unchecked = cache.getUnchecked("key"); // Throws unchecked exception on load failure
74
75
// Bulk loading
76
ImmutableList<String> keys = ImmutableList.of("key1", "key2", "key3");
77
ImmutableMap<String, String> values = cache.getAll(keys); // Uses loadAll if available
78
79
// Refresh (asynchronous reload)
80
cache.refresh("key"); // Triggers background reload, returns stale value immediately
81
82
// Manual override
83
cache.put("key", "manual-value"); // Override loaded value
84
```
85
86
### CacheBuilder Configuration
87
88
Comprehensive configuration options for cache behavior.
89
90
```java { .api }
91
import com.google.common.cache.CacheBuilder;
92
import com.google.common.cache.RemovalListener;
93
import com.google.common.cache.RemovalNotification;
94
import com.google.common.cache.Weigher;
95
96
// Size-based eviction
97
Cache<String, String> sizeCache = CacheBuilder.newBuilder()
98
.maximumSize(1000) // Maximum number of entries
99
.build();
100
101
// Weight-based eviction
102
Cache<String, List<String>> weightCache = CacheBuilder.newBuilder()
103
.maximumWeight(100000)
104
.weigher(new Weigher<String, List<String>>() {
105
@Override
106
public int weigh(String key, List<String> value) {
107
return key.length() + value.size() * 10; // Custom weight calculation
108
}
109
})
110
.build();
111
112
// Time-based expiration
113
Cache<String, String> timeCache = CacheBuilder.newBuilder()
114
.expireAfterWrite(10, TimeUnit.MINUTES) // Expire after write
115
.expireAfterAccess(5, TimeUnit.MINUTES) // Expire after last access
116
.refreshAfterWrite(1, TimeUnit.MINUTES) // Refresh after write (for LoadingCache)
117
.build();
118
119
// Reference-based eviction (memory-sensitive)
120
Cache<String, String> refCache = CacheBuilder.newBuilder()
121
.weakKeys() // Weak references for keys
122
.weakValues() // Weak references for values
123
.softValues() // Soft references for values (alternative to weak)
124
.build();
125
126
// Statistics collection
127
Cache<String, String> statsCache = CacheBuilder.newBuilder()
128
.recordStats() // Enable statistics collection
129
.build();
130
131
// Removal listener
132
Cache<String, String> listenedCache = CacheBuilder.newBuilder()
133
.removalListener(new RemovalListener<String, String>() {
134
@Override
135
public void onRemoval(RemovalNotification<String, String> notification) {
136
String key = notification.getKey();
137
String value = notification.getValue();
138
RemovalCause cause = notification.getCause();
139
System.out.println("Removed: " + key + " -> " + value + " (" + cause + ")");
140
}
141
})
142
.build();
143
144
// Combined configuration
145
Cache<String, ExpensiveObject> productionCache = CacheBuilder.newBuilder()
146
.maximumSize(10000)
147
.expireAfterWrite(30, TimeUnit.MINUTES)
148
.expireAfterAccess(10, TimeUnit.MINUTES)
149
.refreshAfterWrite(5, TimeUnit.MINUTES)
150
.recordStats()
151
.removalListener(loggingRemovalListener)
152
.build();
153
```
154
155
### Advanced CacheLoader Patterns
156
157
Sophisticated loading strategies for different use cases.
158
159
```java { .api }
160
import com.google.common.cache.CacheLoader;
161
import com.google.common.util.concurrent.Futures;
162
import com.google.common.util.concurrent.ListenableFuture;
163
import com.google.common.util.concurrent.ListeningExecutorService;
164
165
// Asynchronous loading
166
CacheLoader<String, String> asyncLoader = new CacheLoader<String, String>() {
167
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
168
Executors.newFixedThreadPool(10));
169
170
@Override
171
public String load(String key) throws Exception {
172
return loadSynchronously(key);
173
}
174
175
@Override
176
public ListenableFuture<String> reload(String key, String oldValue) {
177
return executor.submit(() -> {
178
try {
179
return loadSynchronously(key);
180
} catch (Exception e) {
181
return oldValue; // Return stale value on error
182
}
183
});
184
}
185
186
@Override
187
public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
188
// Efficient bulk loading
189
return batchLoadFromDatabase(keys);
190
}
191
};
192
193
// Fallback loading with error handling
194
CacheLoader<String, String> fallbackLoader = new CacheLoader<String, String>() {
195
@Override
196
public String load(String key) throws Exception {
197
try {
198
return primaryDataSource.load(key);
199
} catch (Exception e) {
200
// Fallback to secondary source
201
String fallback = secondaryDataSource.load(key);
202
if (fallback != null) {
203
return fallback;
204
}
205
throw e; // Re-throw if all sources fail
206
}
207
}
208
};
209
210
// Conditional loading based on key
211
CacheLoader<String, Object> conditionalLoader = new CacheLoader<String, Object>() {
212
@Override
213
public Object load(String key) throws Exception {
214
if (key.startsWith("user:")) {
215
return userService.loadUser(key.substring(5));
216
} else if (key.startsWith("config:")) {
217
return configService.loadConfig(key.substring(7));
218
} else {
219
throw new IllegalArgumentException("Unknown key type: " + key);
220
}
221
}
222
};
223
```
224
225
### Cache Statistics and Monitoring
226
227
Comprehensive statistics for cache performance monitoring.
228
229
```java { .api }
230
import com.google.common.cache.CacheStats;
231
232
// Enable statistics collection
233
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
234
.recordStats()
235
.maximumSize(1000)
236
.build(cacheLoader);
237
238
// Access statistics
239
CacheStats stats = cache.stats();
240
241
// Hit/Miss statistics
242
long requestCount = stats.requestCount(); // Total requests
243
long hitCount = stats.hitCount(); // Cache hits
244
long missCount = stats.missCount(); // Cache misses
245
double hitRate = stats.hitRate(); // Hit rate (0.0 to 1.0)
246
double missRate = stats.missRate(); // Miss rate (0.0 to 1.0)
247
248
// Load statistics
249
long loadCount = stats.loadCount(); // Number of loads
250
double averageLoadPenalty = stats.averageLoadPenalty(); // Average load time in nanoseconds
251
long totalLoadTime = stats.totalLoadTime(); // Total time spent loading
252
253
// Eviction statistics
254
long evictionCount = stats.evictionCount(); // Number of evictions
255
256
// Error statistics (for LoadingCache)
257
long loadExceptionCount = stats.loadExceptionCount(); // Failed loads
258
double loadExceptionRate = stats.loadExceptionRate(); // Load failure rate
259
260
// Monitoring cache performance
261
public void monitorCache(LoadingCache<?, ?> cache) {
262
CacheStats stats = cache.stats();
263
264
if (stats.hitRate() < 0.8) {
265
System.out.println("Warning: Low hit rate - " + stats.hitRate());
266
}
267
268
if (stats.averageLoadPenalty() > TimeUnit.SECONDS.toNanos(1)) {
269
System.out.println("Warning: Slow loading - " + stats.averageLoadPenalty() + "ns");
270
}
271
272
if (stats.evictionCount() > stats.requestCount() * 0.1) {
273
System.out.println("Warning: High eviction rate");
274
}
275
}
276
```
277
278
### Removal Listeners and Cleanup
279
280
Handling cache entry removal for resource cleanup and monitoring.
281
282
```java { .api }
283
import com.google.common.cache.RemovalListener;
284
import com.google.common.cache.RemovalNotification;
285
import com.google.common.cache.RemovalCause;
286
287
// Cleanup removal listener
288
RemovalListener<String, DatabaseConnection> cleanupListener =
289
new RemovalListener<String, DatabaseConnection>() {
290
@Override
291
public void onRemoval(RemovalNotification<String, DatabaseConnection> notification) {
292
DatabaseConnection connection = notification.getValue();
293
if (connection != null) {
294
try {
295
connection.close(); // Cleanup resources
296
} catch (Exception e) {
297
logger.warn("Failed to close connection for key: " + notification.getKey(), e);
298
}
299
}
300
}
301
};
302
303
// Monitoring removal listener
304
RemovalListener<String, String> monitoringListener =
305
new RemovalListener<String, String>() {
306
@Override
307
public void onRemoval(RemovalNotification<String, String> notification) {
308
RemovalCause cause = notification.getCause();
309
310
switch (cause) {
311
case EXPLICIT:
312
// Manual removal via invalidate()
313
break;
314
case REPLACED:
315
// Value replaced with put()
316
break;
317
case COLLECTED:
318
// Garbage collected (weak/soft references)
319
break;
320
case EXPIRED:
321
// Expired based on time
322
break;
323
case SIZE:
324
// Evicted due to size constraints
325
metrics.incrementEvictionCounter();
326
break;
327
}
328
329
logger.debug("Cache entry removed: {} -> {} ({})",
330
notification.getKey(), notification.getValue(), cause);
331
}
332
};
333
334
// Asynchronous removal listener (for expensive cleanup)
335
RemovalListener<String, String> asyncListener = RemovalListeners.asynchronous(
336
expensiveCleanupListener,
337
Executors.newSingleThreadExecutor()
338
);
339
```
340
341
### Cache Patterns and Best Practices
342
343
Common patterns for effective cache usage.
344
345
```java { .api }
346
// Null value handling
347
LoadingCache<String, Optional<String>> nullSafeCache = CacheBuilder.newBuilder()
348
.build(new CacheLoader<String, Optional<String>>() {
349
@Override
350
public Optional<String> load(String key) throws Exception {
351
String value = database.get(key);
352
return Optional.fromNullable(value); // Wrap nulls in Optional
353
}
354
});
355
356
// Usage with null handling
357
Optional<String> result = nullSafeCache.get("key");
358
if (result.isPresent()) {
359
String value = result.get();
360
// Use value
361
}
362
363
// Refresh-ahead pattern
364
LoadingCache<String, String> refreshCache = CacheBuilder.newBuilder()
365
.refreshAfterWrite(5, TimeUnit.MINUTES) // Refresh after 5 minutes
366
.expireAfterWrite(10, TimeUnit.MINUTES) // Expire after 10 minutes
367
.build(new CacheLoader<String, String>() {
368
@Override
369
public String load(String key) throws Exception {
370
return expensiveComputation(key);
371
}
372
373
@Override
374
public ListenableFuture<String> reload(String key, String oldValue) {
375
// Asynchronous reload - returns old value immediately while loading new
376
return backgroundExecutor.submit(() -> expensiveComputation(key));
377
}
378
});
379
380
// Write-through cache pattern
381
public class WritethroughCache {
382
private final LoadingCache<String, String> cache;
383
private final Database database;
384
385
public Writethrough Cache(Database database) {
386
this.database = database;
387
this.cache = CacheBuilder.newBuilder()
388
.maximumSize(1000)
389
.build(new CacheLoader<String, String>() {
390
@Override
391
public String load(String key) throws Exception {
392
return database.get(key);
393
}
394
});
395
}
396
397
public String get(String key) throws ExecutionException {
398
return cache.get(key);
399
}
400
401
public void put(String key, String value) {
402
database.put(key, value); // Write to database first
403
cache.put(key, value); // Then update cache
404
}
405
406
public void remove(String key) {
407
database.remove(key); // Remove from database first
408
cache.invalidate(key); // Then remove from cache
409
}
410
}
411
412
// Multi-level cache
413
public class MultiLevelCache {
414
private final Cache<String, String> l1Cache; // Small, fast cache
415
private final LoadingCache<String, String> l2Cache; // Larger cache
416
417
public MultiLevelCache() {
418
this.l1Cache = CacheBuilder.newBuilder()
419
.maximumSize(100)
420
.expireAfterAccess(1, TimeUnit.MINUTES)
421
.build();
422
423
this.l2Cache = CacheBuilder.newBuilder()
424
.maximumSize(10000)
425
.expireAfterWrite(30, TimeUnit.MINUTES)
426
.build(new CacheLoader<String, String>() {
427
@Override
428
public String load(String key) throws Exception {
429
return database.get(key);
430
}
431
});
432
}
433
434
public String get(String key) throws ExecutionException {
435
// Try L1 cache first
436
String value = l1Cache.getIfPresent(key);
437
if (value != null) {
438
return value;
439
}
440
441
// Fall back to L2 cache
442
value = l2Cache.get(key);
443
l1Cache.put(key, value); // Promote to L1
444
return value;
445
}
446
}
447
```
448
449
### Cache Specification String
450
451
Configure caches using string specifications for external configuration.
452
453
```java { .api }
454
import com.google.common.cache.CacheBuilderSpec;
455
456
// Parse cache specification from string (useful for configuration files)
457
String spec = "maximumSize=1000,expireAfterWrite=30m,recordStats";
458
CacheBuilderSpec builderSpec = CacheBuilderSpec.parse(spec);
459
LoadingCache<String, String> cache = CacheBuilder.from(builderSpec)
460
.build(cacheLoader);
461
462
// Alternative: direct parsing
463
LoadingCache<String, String> cache2 = CacheBuilder.from("maximumSize=500,expireAfterAccess=10m")
464
.recordStats()
465
.build(cacheLoader);
466
467
// Specification format examples:
468
// "maximumSize=1000" - size limit
469
// "maximumWeight=50000" - weight limit
470
// "expireAfterWrite=30m" - expire 30 minutes after write
471
// "expireAfterAccess=1h" - expire 1 hour after access
472
// "refreshAfterWrite=5m" - refresh 5 minutes after write
473
// "weakKeys" - use weak references for keys
474
// "weakValues" - use weak references for values
475
// "softValues" - use soft references for values
476
// "recordStats" - enable statistics
477
```
478
479
### Testing Cache Behavior
480
481
Utilities and patterns for testing cache implementations.
482
483
```java { .api }
484
import com.google.common.testing.FakeTicker;
485
import java.util.concurrent.TimeUnit;
486
487
// Testing time-based expiration
488
public void testCacheExpiration() {
489
FakeTicker ticker = new FakeTicker();
490
491
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
492
.expireAfterWrite(10, TimeUnit.MINUTES)
493
.ticker(ticker) // Use fake ticker for testing
494
.build(cacheLoader);
495
496
// Add entry
497
cache.put("key", "value");
498
assertEquals("value", cache.getIfPresent("key"));
499
500
// Advance time by 5 minutes
501
ticker.advance(5, TimeUnit.MINUTES);
502
assertEquals("value", cache.getIfPresent("key")); // Still present
503
504
// Advance time by another 6 minutes (total 11 minutes)
505
ticker.advance(6, TimeUnit.MINUTES);
506
assertNull(cache.getIfPresent("key")); // Should be expired
507
}
508
509
// Testing cache statistics
510
public void testCacheStats() {
511
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
512
.recordStats()
513
.build(cacheLoader);
514
515
// Trigger cache operations
516
cache.get("key1"); // Miss + Load
517
cache.get("key1"); // Hit
518
cache.get("key2"); // Miss + Load
519
520
CacheStats stats = cache.stats();
521
assertEquals(3, stats.requestCount());
522
assertEquals(1, stats.hitCount());
523
assertEquals(2, stats.missCount());
524
assertEquals(2, stats.loadCount());
525
}
526
```
527
528
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.