0
# Cache Policies
1
2
The `Policy` interface provides runtime access to cache configuration and behavior, allowing inspection and modification of cache policies after creation. This enables dynamic tuning and monitoring of cache performance characteristics.
3
4
## Policy Interface
5
6
```java { .api }
7
public interface Policy<K, V> {
8
// Statistics inspection
9
boolean isRecordingStats();
10
11
// Entry inspection (no side effects)
12
V getIfPresentQuietly(K key);
13
CacheEntry<K, V> getEntryIfPresentQuietly(K key);
14
15
// Active refreshes
16
Map<K, CompletableFuture<V>> refreshes();
17
18
// Policy access
19
Optional<Eviction<K, V>> eviction();
20
Optional<FixedExpiration<K, V>> expireAfterAccess();
21
Optional<FixedExpiration<K, V>> expireAfterWrite();
22
Optional<VarExpiration<K, V>> expireVariably();
23
Optional<FixedRefresh<K, V>> refreshAfterWrite();
24
}
25
26
// Cache entry metadata interface
27
interface CacheEntry<K, V> extends Map.Entry<K, V> {
28
int weight();
29
long expiresAt();
30
Duration expiresAfter();
31
long refreshableAt();
32
Duration refreshableAfter();
33
long snapshotAt();
34
}
35
```
36
37
### Accessing Cache Policies
38
39
```java
40
Cache<String, String> cache = Caffeine.newBuilder()
41
.maximumSize(1000)
42
.expireAfterAccess(Duration.ofMinutes(30))
43
.expireAfterWrite(Duration.ofHours(1))
44
.refreshAfterWrite(Duration.ofMinutes(10))
45
.recordStats()
46
.build();
47
48
Policy<String, String> policy = cache.policy();
49
50
// Check if statistics are enabled
51
boolean recordingStats = policy.isRecordingStats(); // true
52
53
// Access available policies
54
Optional<Policy.Eviction<String, String>> eviction = policy.eviction();
55
Optional<Policy.Expiration<String, String>> accessExpiration = policy.expireAfterAccess();
56
Optional<Policy.Expiration<String, String>> writeExpiration = policy.expireAfterWrite();
57
Optional<Policy.Expiration<String, String>> refreshPolicy = policy.refreshAfterWrite();
58
```
59
60
## Eviction Policy
61
62
The eviction policy controls size-based and weight-based cache limits.
63
64
```java { .api }
65
interface Eviction<K, V> {
66
// Size inspection and modification
67
boolean isWeighted();
68
OptionalInt weightOf(K key);
69
OptionalLong weightedSize();
70
long getMaximum();
71
void setMaximum(long maximum);
72
73
// Entry inspection by count
74
Map<K, V> coldest(int limit);
75
Map<K, V> hottest(int limit);
76
77
// Entry inspection by weight (v3.0.4+)
78
Map<K, V> coldestWeighted(long weightLimit);
79
Map<K, V> hottestWeighted(long weightLimit);
80
81
// Stream-based entry inspection (v3.0.6+)
82
<T> T coldest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
83
<T> T hottest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
84
}
85
```
86
87
### Size-Based Eviction
88
89
```java
90
Cache<String, String> sizeCache = Caffeine.newBuilder()
91
.maximumSize(1000)
92
.build();
93
94
Policy.Eviction<String, String> evictionPolicy = sizeCache.policy().eviction().get();
95
96
// Inspect current configuration
97
boolean isWeighted = evictionPolicy.isWeighted(); // false for size-based
98
long maxSize = evictionPolicy.getMaximum().getAsLong(); // 1000
99
100
// Dynamically adjust maximum size
101
evictionPolicy.setMaximum(2000);
102
long newMaxSize = evictionPolicy.getMaximum().getAsLong(); // 2000
103
104
// Inspect hottest and coldest entries
105
sizeCache.put("hot1", "value1");
106
sizeCache.put("hot2", "value2");
107
sizeCache.put("cold1", "value1");
108
sizeCache.put("cold2", "value2");
109
110
// Access patterns affect hotness
111
sizeCache.getIfPresent("hot1");
112
sizeCache.getIfPresent("hot2");
113
114
Map<String, String> hottestEntries = evictionPolicy.hottest(2);
115
Map<String, String> coldestEntries = evictionPolicy.coldest(2);
116
```
117
118
### Weight-Based Eviction
119
120
```java
121
Cache<String, String> weightCache = Caffeine.newBuilder()
122
.maximumWeight(10000)
123
.weigher((key, value) -> key.length() + value.length())
124
.build();
125
126
Policy.Eviction<String, String> weightEvictionPolicy = weightCache.policy().eviction().get();
127
128
// Weight-specific operations
129
boolean isWeighted = weightEvictionPolicy.isWeighted(); // true
130
long maxWeight = weightEvictionPolicy.getMaximum().getAsLong(); // 10000
131
long currentWeight = weightEvictionPolicy.weightedSize().getAsLong();
132
133
// Adjust maximum weight dynamically
134
weightEvictionPolicy.setMaximum(20000);
135
136
System.out.println("Current weight: " + currentWeight + "/" + maxWeight);
137
```
138
139
## Expiration Policies
140
141
Expiration policies control time-based entry removal and provide runtime inspection.
142
143
```java { .api }
144
interface FixedExpiration<K, V> {
145
// Duration inspection and modification
146
OptionalLong ageOf(K key, TimeUnit unit);
147
Duration ageOf(K key);
148
long getExpiresAfter(TimeUnit unit);
149
Duration getExpiresAfter();
150
void setExpiresAfter(long duration, TimeUnit unit);
151
void setExpiresAfter(Duration duration);
152
153
// Entry inspection by count
154
Map<K, V> oldest(int limit);
155
Map<K, V> youngest(int limit);
156
157
// Stream-based entry inspection (v3.0.6+)
158
<T> T oldest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
159
<T> T youngest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
160
}
161
162
interface VarExpiration<K, V> {
163
// Variable expiration inspection and modification
164
OptionalLong getExpiresAfter(K key, TimeUnit unit);
165
Optional<Duration> getExpiresAfter(K key);
166
void setExpiresAfter(K key, long duration, TimeUnit unit);
167
void setExpiresAfter(K key, Duration duration);
168
169
// Entry modification with custom expiration
170
V putIfAbsent(K key, V value, long duration, TimeUnit unit);
171
V putIfAbsent(K key, V value, Duration duration);
172
V put(K key, V value, long duration, TimeUnit unit);
173
V put(K key, V value, Duration duration);
174
V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction, Duration duration);
175
176
// Age and entry inspection
177
OptionalLong ageOf(K key, TimeUnit unit);
178
Map<K, V> oldest(int limit);
179
Map<K, V> youngest(int limit);
180
181
// Stream-based entry inspection (v3.0.6+)
182
<T> T oldest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
183
<T> T youngest(Function<Stream<CacheEntry<K, V>>, T> mappingFunction);
184
}
185
186
interface FixedRefresh<K, V> {
187
OptionalLong ageOf(K key, TimeUnit unit);
188
Duration ageOf(K key);
189
long getRefreshesAfter(TimeUnit unit);
190
Duration getRefreshesAfter();
191
void setRefreshesAfter(long duration, TimeUnit unit);
192
void setRefreshesAfter(Duration duration);
193
}
194
```
195
196
### Fixed Expiration Policies
197
198
```java
199
Cache<String, String> expiringCache = Caffeine.newBuilder()
200
.maximumSize(1000)
201
.expireAfterAccess(Duration.ofMinutes(30))
202
.expireAfterWrite(Duration.ofHours(2))
203
.build();
204
205
// Access expiration policy
206
Policy.Expiration<String, String> accessExpiration =
207
expiringCache.policy().expireAfterAccess().get();
208
209
// Inspect current expiration settings
210
Duration accessDuration = accessExpiration.getExpiresAfter().get(); // 30 minutes
211
212
// Modify expiration duration at runtime
213
accessExpiration.setExpiresAfter(Duration.ofMinutes(45));
214
215
// Write expiration policy
216
Policy.Expiration<String, String> writeExpiration =
217
expiringCache.policy().expireAfterWrite().get();
218
219
Duration writeDuration = writeExpiration.getExpiresAfter().get(); // 2 hours
220
writeExpiration.setExpiresAfter(Duration.ofHours(3));
221
222
// Inspect entry ages
223
expiringCache.put("test_key", "test_value");
224
Thread.sleep(1000);
225
226
OptionalLong ageInSeconds = accessExpiration.ageOf("test_key", TimeUnit.SECONDS);
227
if (ageInSeconds.isPresent()) {
228
System.out.println("Entry age: " + ageInSeconds.getAsLong() + " seconds");
229
}
230
231
// Get oldest and youngest entries
232
Map<String, String> oldestEntries = accessExpiration.oldest(5);
233
Map<String, String> youngestEntries = accessExpiration.youngest(5);
234
```
235
236
### Variable Expiration Policy
237
238
```java
239
Cache<String, String> varExpiringCache = Caffeine.newBuilder()
240
.maximumSize(1000)
241
.expireAfter(Expiry.creating((key, value) -> {
242
// Different expiration based on key prefix
243
if (key.startsWith("temp_")) {
244
return Duration.ofMinutes(5);
245
} else if (key.startsWith("cache_")) {
246
return Duration.ofHours(1);
247
} else {
248
return Duration.ofMinutes(30);
249
}
250
}))
251
.build();
252
253
Policy.VarExpiration<String, String> varExpiration =
254
varExpiringCache.policy().expireVariably().get();
255
256
// Add entries with different expiration times
257
varExpiringCache.put("temp_data", "temporary");
258
varExpiringCache.put("cache_data", "cached");
259
varExpiringCache.put("normal_data", "normal");
260
261
// Inspect individual entry expiration
262
OptionalLong tempExpiration = varExpiration.getExpiresAfter("temp_data", TimeUnit.MINUTES);
263
OptionalLong cacheExpiration = varExpiration.getExpiresAfter("cache_data", TimeUnit.MINUTES);
264
265
System.out.println("Temp expires in: " + tempExpiration.orElse(-1) + " minutes");
266
System.out.println("Cache expires in: " + cacheExpiration.orElse(-1) + " minutes");
267
268
// Modify expiration for specific entries
269
varExpiration.setExpiresAfter("temp_data", 10, TimeUnit.MINUTES);
270
271
// Inspect ages
272
OptionalLong tempAge = varExpiration.ageOf("temp_data", TimeUnit.SECONDS);
273
System.out.println("Temp entry age: " + tempAge.orElse(-1) + " seconds");
274
```
275
276
## Refresh Policy
277
278
The refresh policy controls automatic background refresh behavior for loading caches.
279
280
```java
281
LoadingCache<String, String> refreshingCache = Caffeine.newBuilder()
282
.maximumSize(1000)
283
.refreshAfterWrite(Duration.ofMinutes(5))
284
.build(key -> "loaded_" + System.currentTimeMillis());
285
286
Policy.Expiration<String, String> refreshPolicy =
287
refreshingCache.policy().refreshAfterWrite().get();
288
289
// Inspect refresh configuration
290
Duration refreshInterval = refreshPolicy.getExpiresAfter().get(); // 5 minutes
291
292
// Modify refresh interval
293
refreshPolicy.setExpiresAfter(Duration.ofMinutes(10));
294
295
// Check time since last refresh
296
refreshingCache.get("test_key");
297
Thread.sleep(1000);
298
299
OptionalLong timeSinceRefresh = refreshPolicy.ageOf("test_key", TimeUnit.SECONDS);
300
System.out.println("Time since last refresh: " + timeSinceRefresh.orElse(-1) + " seconds");
301
302
// Get entries that need refresh soon
303
Map<String, String> oldestEntries = refreshPolicy.oldest(10);
304
System.out.println("Entries needing refresh soon: " + oldestEntries.keySet());
305
```
306
307
## Policy Inspection Examples
308
309
### Complete Policy Inspection
310
311
```java
312
Cache<String, String> fullyConfiguredCache = Caffeine.newBuilder()
313
.maximumSize(10000)
314
.expireAfterAccess(Duration.ofMinutes(30))
315
.expireAfterWrite(Duration.ofHours(2))
316
.recordStats()
317
.build();
318
319
Policy<String, String> policy = fullyConfiguredCache.policy();
320
321
// Check all available policies
322
System.out.println("Recording stats: " + policy.isRecordingStats());
323
324
if (policy.eviction().isPresent()) {
325
Policy.Eviction<String, String> eviction = policy.eviction().get();
326
System.out.println("Max size: " + eviction.getMaximum().orElse(-1));
327
System.out.println("Is weighted: " + eviction.isWeighted());
328
}
329
330
if (policy.expireAfterAccess().isPresent()) {
331
Policy.Expiration<String, String> accessExp = policy.expireAfterAccess().get();
332
System.out.println("Expire after access: " + accessExp.getExpiresAfter().orElse(null));
333
}
334
335
if (policy.expireAfterWrite().isPresent()) {
336
Policy.Expiration<String, String> writeExp = policy.expireAfterWrite().get();
337
System.out.println("Expire after write: " + writeExp.getExpiresAfter().orElse(null));
338
}
339
340
// Variable expiration and refresh would be empty for this cache
341
System.out.println("Has variable expiration: " + policy.expireVariably().isPresent());
342
System.out.println("Has refresh policy: " + policy.refreshAfterWrite().isPresent());
343
```
344
345
### Dynamic Policy Adjustment
346
347
```java
348
Cache<String, String> adaptiveCache = Caffeine.newBuilder()
349
.maximumSize(1000)
350
.expireAfterAccess(Duration.ofMinutes(10))
351
.build();
352
353
Policy<String, String> policy = adaptiveCache.policy();
354
355
// Monitor cache performance and adjust policies
356
Timer timer = new Timer();
357
timer.scheduleAtFixedRate(new TimerTask() {
358
@Override
359
public void run() {
360
CacheStats stats = adaptiveCache.stats();
361
double hitRate = stats.hitRate();
362
363
// Adjust expiration based on hit rate
364
if (hitRate < 0.8 && policy.expireAfterAccess().isPresent()) {
365
// Low hit rate - increase expiration time
366
Duration current = policy.expireAfterAccess().get().getExpiresAfter().get();
367
Duration increased = current.multipliedBy(2);
368
policy.expireAfterAccess().get().setExpiresAfter(increased);
369
System.out.println("Increased expiration to: " + increased);
370
}
371
372
// Adjust size based on eviction rate
373
if (stats.evictionCount() > 100 && policy.eviction().isPresent()) {
374
// High eviction rate - increase cache size
375
long currentMax = policy.eviction().get().getMaximum().getAsLong();
376
policy.eviction().get().setMaximum(currentMax * 2);
377
System.out.println("Increased max size to: " + (currentMax * 2));
378
}
379
}
380
}, 60000, 60000); // Check every minute
381
```
382
383
## Policy Limitations
384
385
- **Async Cache Policies**: Async caches return policies that operate on the underlying synchronous cache
386
- **Configuration Constraints**: Some policy modifications may be rejected if they violate cache constraints
387
- **Thread Safety**: Policy operations are thread-safe but modifications should be done carefully in concurrent environments
388
- **Performance Impact**: Frequent policy modifications may impact cache performance