• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #5612

11 Jul 2026 03:47PM UTC coverage: 71.292% (-28.7%) from 100.0%
#5612

push

github

ben-manes
Preserve timestamps on a raced same-instance refresh

The automatic refreshIfNeeded path checked for an equal-value reload
before the ABA/ownership check and returned without preserving the
timestamps, so a reload completing after a concurrent put(k,
sameInstance) did a full update — shifting the entry's expiration
forward by the reload's in-flight duration and stomping the write.
Mirror the explicit-refresh fix: check ownership first. An owned
reload installs the value (refreshing metadata even for a same
instance), while a raced reload preserves the write's timestamps and
only notifies REPLACED when the value differs.

2957 of 4134 branches covered (71.53%)

3 of 3 new or added lines in 1 file covered. (100.0%)

2408 existing lines in 53 files now uncovered.

5980 of 8388 relevant lines covered (71.29%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.59
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Policy.java
1
/*
2
 * Copyright 2014 Ben Manes. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.github.benmanes.caffeine.cache;
17

18
import static com.github.benmanes.caffeine.cache.Caffeine.toNanosSaturated;
19

20
import java.time.Duration;
21
import java.util.Map;
22
import java.util.Optional;
23
import java.util.OptionalInt;
24
import java.util.OptionalLong;
25
import java.util.concurrent.CompletableFuture;
26
import java.util.concurrent.TimeUnit;
27
import java.util.function.BiFunction;
28
import java.util.function.Function;
29
import java.util.stream.Stream;
30

31
import org.jspecify.annotations.NullMarked;
32
import org.jspecify.annotations.Nullable;
33

34
/**
35
 * An access point for inspecting and performing low-level operations based on the cache's runtime
36
 * characteristics. These operations are optional and dependent on how the cache was constructed
37
 * and what abilities the implementation exposes.
38
 *
39
 * @param <K> the type of keys
40
 * @param <V> the type of values
41
 * @author ben.manes@gmail.com (Ben Manes)
42
 */
43
@NullMarked
44
public interface Policy<K, V> {
45

46
  /**
47
   * Returns whether the cache statistics are being accumulated.
48
   *
49
   * @return if cache statistics are being recorded
50
   */
51
  boolean isRecordingStats();
52

53
  /**
54
   * Returns the value associated with the {@code key} in this cache, or {@code null} if there is no
55
   * cached value for the {@code key}. Unlike {@link Cache#getIfPresent(Object)}, this method does
56
   * not produce any side effects such as updating statistics, the eviction policy, resetting the
57
   * expiration time, or triggering a refresh.
58
   *
59
   * @param key the key whose associated value is to be returned
60
   * @return the value to which the specified key is mapped, or {@code null} if this cache contains
61
   *         no mapping for the key
62
   * @throws NullPointerException if the specified key is null
63
   */
64
  @Nullable V getIfPresentQuietly(K key);
65

66
  /**
67
   * Returns the cache entry associated with the {@code key} in this cache, or {@code null} if there
68
   * is no cached value for the {@code key}. Unlike {@link Cache#getIfPresent(Object)}, this method
69
   * does not produce any side effects such as updating statistics, the eviction policy, resetting
70
   * the expiration time, or triggering a refresh.
71
   *
72
   * @param key the key whose associated value is to be returned
73
   * @return the entry mapping for the specified key, or {@code null} if this cache contains no
74
   *         mapping for the key
75
   * @throws NullPointerException if the specified key is null
76
   * @since 3.0.6
77
   */
78
  default @Nullable CacheEntry<K, V> getEntryIfPresentQuietly(K key) {
UNCOV
79
    throw new UnsupportedOperationException();
×
80
  }
81

82
  /**
83
   * Returns an unmodifiable snapshot {@link Map} view of the in-flight refresh operations.
84
   *
85
   * @return a snapshot view of the in-flight refresh operations
86
   */
87
  Map<K, CompletableFuture<V>> refreshes();
88

89
  /**
90
   * Returns access to perform operations based on the maximum size or maximum weight eviction
91
   * policy. If the cache was not constructed with a size-based bound or the implementation does
92
   * not support these operations, an empty {@link Optional} is returned.
93
   *
94
   * @return access to low-level operations for this cache if an eviction policy is used
95
   */
96
  Optional<Eviction<K, V>> eviction();
97

98
  /**
99
   * Returns access to perform operations based on the time-to-idle expiration policy. This policy
100
   * determines that an entry should be automatically removed from the cache once a fixed duration
101
   * has elapsed after the entry's creation, the most recent replacement of its value, or its last
102
   * access. Access time is reset by all cache read and write operations (including
103
   * {@code Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by operations
104
   * on the collection-views of {@link Cache#asMap}.
105
   * <p>
106
   * If the cache was not constructed with access-based expiration or the implementation does not
107
   * support these operations, an empty {@link Optional} is returned.
108
   *
109
   * @return access to low-level operations for this cache if a time-to-idle expiration policy is
110
   *         used
111
   */
112
  Optional<FixedExpiration<K, V>> expireAfterAccess();
113

114
  /**
115
   * Returns access to perform operations based on the time-to-live expiration policy. This policy
116
   * determines that an entry should be automatically removed from the cache once a fixed duration
117
   * has elapsed after the entry's creation, or the most recent replacement of its value.
118
   * <p>
119
   * If the cache was not constructed with write-based expiration or the implementation does not
120
   * support these operations, an empty {@link Optional} is returned.
121
   *
122
   * @return access to low-level operations for this cache if a time-to-live expiration policy is
123
   *         used
124
   */
125
  Optional<FixedExpiration<K, V>> expireAfterWrite();
126

127
  /**
128
   * Returns access to perform operations based on the variable expiration policy. This policy
129
   * determines that an entry should be automatically removed from the cache once a per-entry
130
   * duration has elapsed.
131
   * <p>
132
   * If the cache was not constructed with variable expiration or the implementation does not
133
   * support these operations, an empty {@link Optional} is returned.
134
   *
135
   * @return access to low-level operations for this cache if a variable expiration policy is used
136
   */
137
  Optional<VarExpiration<K, V>> expireVariably();
138

139
  /**
140
   * Returns access to perform operations based on the time-to-live refresh policy. This policy
141
   * determines that an entry should be automatically reloaded once a fixed duration has elapsed
142
   * after the entry's creation, or the most recent replacement of its value.
143
   * <p>
144
   * If the cache was not constructed with write-based refresh or the implementation does not
145
   * support these operations, an empty {@link Optional} is returned.
146
   *
147
   * @return access to low-level operations for this cache if a time-to-live refresh policy is used
148
   */
149
  Optional<FixedRefresh<K, V>> refreshAfterWrite();
150

151
  /**
152
   * The low-level operations for a cache with a size-based eviction policy.
153
   *
154
   * @param <K> the type of keys
155
   * @param <V> the type of values
156
   */
157
  interface Eviction<K, V> {
158

159
    /**
160
     * Returns whether the cache is bounded by a maximum size or maximum weight.
161
     *
162
     * @return if the size bounding takes into account the entry's weight
163
     */
164
    boolean isWeighted();
165

166
    /**
167
     * Returns the weight of the entry. If this cache does not use a weighted size bound or does not
168
     * support querying for the entry's weight, then the {@link OptionalInt} will be empty. In an
169
     * asynchronous cache while the future is incomplete then the weight may be zero.
170
     *
171
     * @param key the key for the entry being queried
172
     * @return the weight if the entry is present in the cache
173
     * @throws NullPointerException if the specified key is null
174
     */
175
    OptionalInt weightOf(K key);
176

177
    /**
178
     * Returns the approximate accumulated weight of entries in this cache. If this cache does not
179
     * use a weighted size bound, then the {@link OptionalLong} will be empty.
180
     *
181
     * @return the combined weight of the values in this cache
182
     */
183
    OptionalLong weightedSize();
184

185
    /**
186
     * Returns the maximum total weighted or unweighted size of this cache, depending on how the
187
     * cache was constructed. This value can be best understood by inspecting {@link #isWeighted()}.
188
     *
189
     * @return the maximum size bounding, which may be either weighted or unweighted
190
     */
191
    long getMaximum();
192

193
    /**
194
     * Specifies the maximum total size of this cache. This value may be interpreted as the weighted
195
     * or unweighted threshold size based on how this cache was constructed. If the cache currently
196
     * exceeds the new maximum size this operation eagerly evict entries until the cache shrinks to
197
     * the appropriate size.
198
     * <p>
199
     * Note that some implementations may have an internal inherent bound on the maximum total size.
200
     * If the value specified exceeds that bound, then the value is set to the internal maximum.
201
     *
202
     * @param maximum the maximum, interpreted as weighted or unweighted size depending on how this
203
     *        cache was constructed
204
     * @throws IllegalArgumentException if the maximum size specified is negative
205
     */
206
    void setMaximum(long maximum);
207

208
    /**
209
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
210
     * order of iteration is from the entries least likely to be retained (coldest) to the entries
211
     * most likely to be retained (hottest). This order is determined by the eviction policy's best
212
     * guess at the time of creating this snapshot view.
213
     * <p>
214
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
215
     * asynchronous nature of the page replacement policy, determining the retention ordering
216
     * requires a traversal of the entries within the eviction policy's exclusive lock.
217
     *
218
     * @param limit the maximum size of the returned map (use {@link Integer#MAX_VALUE} to disregard
219
     *        the limit)
220
     * @return a snapshot view of the cache from the coldest entry to the hottest
221
     * @throws IllegalArgumentException if the limit specified is negative
222
     */
223
    Map<K, V> coldest(int limit);
224

225
    /**
226
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
227
     * order of iteration is from the entries least likely to be retained (coldest) to the entries
228
     * most likely to be retained (hottest). This order is determined by the eviction policy's best
229
     * guess at the time of creating this snapshot view. If the cache is bounded by a maximum size
230
     * rather than a maximum weight, then this method is equivalent to {@link #coldest(int)}.
231
     * <p>
232
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
233
     * asynchronous nature of the page replacement policy, determining the retention ordering
234
     * requires a traversal of the entries within the eviction policy's exclusive lock.
235
     *
236
     * @param weightLimit the maximum weight of the returned map (use {@link Long#MAX_VALUE} to
237
     *        disregard the limit)
238
     * @return a snapshot view of the cache from the coldest entry to the hottest
239
     * @throws IllegalArgumentException if the limit specified is negative
240
     * @since 3.0.4
241
     */
242
    default Map<K, V> coldestWeighted(long weightLimit) {
UNCOV
243
      throw new UnsupportedOperationException();
×
244
    }
245

246
    /**
247
     * Returns the computed result from the ordered traversal of the cache entries. The order of
248
     * iteration is from the entries least likely to be retained (coldest) to the entries most
249
     * likely to be retained (hottest). This order is determined by the eviction policy's best guess
250
     * at the time of creating this computation.
251
     * <p>
252
     * Usage example:
253
     * {@snippet class=com.github.benmanes.caffeine.cache.Snippets
254
     *           region=eviction_coldest lang=java}
255
     * <p>
256
     * Beware that this computation is performed within the eviction policy's exclusive lock, so the
257
     * computation should be short and simple. While the computation is in progress further eviction
258
     * maintenance will be halted.
259
     *
260
     * @param <T> the type of the result of the mappingFunction
261
     * @param mappingFunction the mapping function to compute a value
262
     * @return the computed value
263
     * @throws NullPointerException if the mappingFunction is null
264
     * @throws RuntimeException or Error if the mappingFunction does so
265
     * @throws java.util.ConcurrentModificationException if the computation detectably reads or
266
     *         writes an entry in this cache
267
     * @since 3.0.6
268
     */
269
    @SuppressWarnings({"JavadocDeclaration", "JavadocReference"})
270
    default <T extends @Nullable Object> T coldest(
271
        Function<Stream<CacheEntry<K, V>>, T> mappingFunction) {
UNCOV
272
      throw new UnsupportedOperationException();
×
273
    }
274

275
    /**
276
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
277
     * order of iteration is from the entries most likely to be retained (hottest) to the entries
278
     * least likely to be retained (coldest). This order is determined by the eviction policy's best
279
     * guess at the time of creating this snapshot view.
280
     * <p>
281
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
282
     * asynchronous nature of the page replacement policy, determining the retention ordering
283
     * requires a traversal of the entries within the eviction policy's exclusive lock.
284
     *
285
     * @param limit the maximum size of the returned map (use {@link Integer#MAX_VALUE} to disregard
286
     *        the limit)
287
     * @return a snapshot view of the cache from the hottest entry to the coldest
288
     * @throws IllegalArgumentException if the limit specified is negative
289
     */
290
    Map<K, V> hottest(int limit);
291

292
    /**
293
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
294
     * order of iteration is from the entries most likely to be retained (hottest) to the entries
295
     * least likely to be retained (coldest). This order is determined by the eviction policy's best
296
     * guess at the time of creating this snapshot view. If the cache is bounded by a maximum size
297
     * rather than a maximum weight, then this method is equivalent to {@link #hottest(int)}.
298
     * <p>
299
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
300
     * asynchronous nature of the page replacement policy, determining the retention ordering
301
     * requires a traversal of the entries within the eviction policy's exclusive lock.
302
     *
303
     * @param weightLimit the maximum weight of the returned map (use {@link Long#MAX_VALUE} to
304
     *        disregard the limit)
305
     * @return a snapshot view of the cache from the hottest entry to the coldest
306
     * @throws IllegalArgumentException if the limit specified is negative
307
     * @since 3.0.4
308
     */
309
    default Map<K, V> hottestWeighted(long weightLimit) {
UNCOV
310
      throw new UnsupportedOperationException();
×
311
    }
312

313
    /**
314
     * Returns the computed result from the ordered traversal of the cache entries. The order of
315
     * iteration is from the entries most likely to be retained (hottest) to the entries least
316
     * likely to be retained (coldest). This order is determined by the eviction policy's best guess
317
     * at the time of creating this computation.
318
     * <p>
319
     * Usage example:
320
     * {@snippet class=com.github.benmanes.caffeine.cache.Snippets
321
     *           region=eviction_hottest lang=java}
322
     * <p>
323
     * Beware that this computation is performed within the eviction policy's exclusive lock, so the
324
     * computation should be short and simple. While the computation is in progress further eviction
325
     * maintenance will be halted.
326
     *
327
     * @param <T> the type of the result of the mappingFunction
328
     * @param mappingFunction the mapping function to compute a value
329
     * @return the computed value
330
     * @throws NullPointerException if the mappingFunction is null
331
     * @throws RuntimeException or Error if the mappingFunction does so
332
     * @throws java.util.ConcurrentModificationException if the computation detectably reads or
333
     *         writes an entry in this cache
334
     * @since 3.0.6
335
     */
336
    @SuppressWarnings({"JavadocDeclaration", "JavadocReference"})
337
    default <T extends @Nullable Object> T hottest(
338
        Function<Stream<CacheEntry<K, V>>, T> mappingFunction) {
UNCOV
339
      throw new UnsupportedOperationException();
×
340
    }
341
  }
342

343
  /**
344
   * The low-level operations for a cache with a fixed expiration policy.
345
   *
346
   * @param <K> the type of keys
347
   * @param <V> the type of values
348
   */
349
  interface FixedExpiration<K, V> {
350

351
    /**
352
     * Returns the age of the entry based on the expiration policy. The entry's age is the cache's
353
     * estimate of the amount of time since the entry's expiration was last reset. In an
354
     * asynchronous cache while the future is incomplete then the duration may be negative.
355
     * <p>
356
     * An expiration policy uses the age to determine if an entry is fresh or stale by comparing it
357
     * to the freshness lifetime. This is calculated as {@code fresh = freshnessLifetime > age}
358
     * where {@code freshnessLifetime = expires - currentTime}.
359
     *
360
     * @param key the key for the entry being queried
361
     * @param unit the unit that {@code age} is expressed in
362
     * @return the age if the entry is present in the cache
363
     * @throws NullPointerException if the specified key is null
364
     */
365
    OptionalLong ageOf(K key, TimeUnit unit);
366

367
    /**
368
     * Returns the age of the entry based on the expiration policy. The entry's age is the cache's
369
     * estimate of the amount of time since the entry's expiration was last reset. In an
370
     * asynchronous cache while the future is incomplete then the duration may be negative.
371
     * <p>
372
     * An expiration policy uses the age to determine if an entry is fresh or stale by comparing it
373
     * to the freshness lifetime. This is calculated as {@code fresh = freshnessLifetime > age}
374
     * where {@code freshnessLifetime = expires - currentTime}.
375
     *
376
     * @param key the key for the entry being queried
377
     * @return the age if the entry is present in the cache
378
     * @throws NullPointerException if the specified key is null
379
     */
380
    default Optional<Duration> ageOf(K key) {
381
      OptionalLong duration = ageOf(key, TimeUnit.NANOSECONDS);
1✔
382
      return duration.isPresent()
1✔
383
          ? Optional.of(Duration.ofNanos(duration.getAsLong()))
1✔
384
          : Optional.empty();
1✔
385
    }
386

387
    /**
388
     * Returns the fixed duration used to determine if an entry should be automatically removed due
389
     * to elapsing this time bound. An entry is considered fresh if its age is less than this
390
     * duration, and stale otherwise. The expiration policy determines when the entry's age is
391
     * reset.
392
     *
393
     * @param unit the unit that duration is expressed in
394
     * @return the length of time after which an entry should be automatically removed
395
     * @throws NullPointerException if the unit is null
396
     */
397
    long getExpiresAfter(TimeUnit unit);
398

399
    /**
400
     * Returns the fixed duration used to determine if an entry should be automatically removed due
401
     * to elapsing this time bound. An entry is considered fresh if its age is less than this
402
     * duration, and stale otherwise. The expiration policy determines when the entry's age is
403
     * reset.
404
     *
405
     * @return the length of time after which an entry should be automatically removed
406
     */
407
    default Duration getExpiresAfter() {
408
      return Duration.ofNanos(getExpiresAfter(TimeUnit.NANOSECONDS));
1✔
409
    }
410

411
    /**
412
     * Specifies that each entry should be automatically removed from the cache once a fixed
413
     * duration has elapsed. The expiration policy determines when the entry's age is reset.
414
     *
415
     * @param duration the length of time after which an entry should be automatically removed
416
     * @param unit the unit that {@code duration} is expressed in
417
     * @throws IllegalArgumentException if {@code duration} is negative
418
     * @throws NullPointerException if the unit is null
419
     */
420
    void setExpiresAfter(long duration, TimeUnit unit);
421

422
    /**
423
     * Specifies that each entry should be automatically removed from the cache once a fixed
424
     * duration has elapsed. The expiration policy determines when the entry's age is reset.
425
     *
426
     * @param duration the length of time after which an entry should be automatically removed
427
     * @throws IllegalArgumentException if {@code duration} is negative
428
     * @throws NullPointerException if the duration is null
429
     */
430
    default void setExpiresAfter(Duration duration) {
431
      setExpiresAfter(toNanosSaturated(duration), TimeUnit.NANOSECONDS);
1✔
432
    }
1✔
433

434
    /**
435
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
436
     * order of iteration is from the entries most likely to expire (oldest) to the entries least
437
     * likely to expire (youngest). This order is determined by the expiration policy's best guess
438
     * at the time of creating this snapshot view.
439
     * <p>
440
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
441
     * asynchronous nature of the page replacement policy, determining the retention ordering
442
     * requires a traversal of the entries.
443
     *
444
     * @param limit the maximum size of the returned map (use {@link Integer#MAX_VALUE} to disregard
445
     *        the limit)
446
     * @return a snapshot view of the cache from the oldest entry to the youngest
447
     * @throws IllegalArgumentException if the limit specified is negative
448
     */
449
    Map<K, V> oldest(int limit);
450

451
    /**
452
     * Returns the computed result from the ordered traversal of the cache entries. The order of
453
     * iteration is from the entries most likely to expire (oldest) to the entries least likely to
454
     * expire (youngest). This order is determined by the expiration policy's best guess at the time
455
     * of creating this computation.
456
     * <p>
457
     * Usage example:
458
     * {@snippet class=com.github.benmanes.caffeine.cache.Snippets
459
     *           region=expireFixed_oldest lang=java}
460
     * <p>
461
     * Beware that this computation is performed within the eviction policy's exclusive lock, so the
462
     * computation should be short and simple. While the computation is in progress further eviction
463
     * maintenance will be halted.
464
     *
465
     * @param <T> the type of the result of the mappingFunction
466
     * @param mappingFunction the mapping function to compute a value
467
     * @return the computed value
468
     * @throws NullPointerException if the mappingFunction is null
469
     * @throws RuntimeException or Error if the mappingFunction does so
470
     * @throws java.util.ConcurrentModificationException if the computation detectably reads or
471
     *         writes an entry in this cache
472
     * @since 3.0.6
473
     */
474
    @SuppressWarnings({"JavadocDeclaration", "JavadocReference"})
475
    default <T extends @Nullable Object> T oldest(
476
        Function<Stream<CacheEntry<K, V>>, T> mappingFunction) {
UNCOV
477
      throw new UnsupportedOperationException();
×
478
    }
479

480
    /**
481
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
482
     * order of iteration is from the entries least likely to expire (youngest) to the entries most
483
     * likely to expire (oldest). This order is determined by the expiration policy's best guess at
484
     * the time of creating this snapshot view.
485
     * <p>
486
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
487
     * asynchronous nature of the page replacement policy, determining the retention ordering
488
     * requires a traversal of the entries.
489
     *
490
     * @param limit the maximum size of the returned map (use {@link Integer#MAX_VALUE} to disregard
491
     *        the limit)
492
     * @return a snapshot view of the cache from the youngest entry to the oldest
493
     * @throws IllegalArgumentException if the limit specified is negative
494
     */
495
    Map<K, V> youngest(int limit);
496

497
    /**
498
     * Returns the computed result from the ordered traversal of the cache entries. The order of
499
     * iteration is from the entries least likely to expire (youngest) to the entries most likely to
500
     * expire (oldest). This order is determined by the expiration policy's best guess at the time
501
     * of creating this computation.
502
     * <p>
503
     * Usage example:
504
     * {@snippet class=com.github.benmanes.caffeine.cache.Snippets
505
     *           region=expireFixed_youngest lang=java}
506
     * <p>
507
     * Beware that this computation is performed within the eviction policy's exclusive lock, so the
508
     * computation should be short and simple. While the computation is in progress further eviction
509
     * maintenance will be halted.
510
     *
511
     * @param <T> the type of the result of the mappingFunction
512
     * @param mappingFunction the mapping function to compute a value
513
     * @return the computed value
514
     * @throws NullPointerException if the mappingFunction is null
515
     * @throws RuntimeException or Error if the mappingFunction does so
516
     * @throws java.util.ConcurrentModificationException if the computation detectably reads or
517
     *         writes an entry in this cache
518
     * @since 3.0.6
519
     */
520
    @SuppressWarnings({"JavadocDeclaration", "JavadocReference"})
521
    default <T extends @Nullable Object> T youngest(
522
        Function<Stream<CacheEntry<K, V>>, T> mappingFunction) {
UNCOV
523
      throw new UnsupportedOperationException();
×
524
    }
525
  }
526

527
  /**
528
   * The low-level operations for a cache with a variable expiration policy.
529
   *
530
   * @param <K> the type of keys
531
   * @param <V> the type of values
532
   */
533
  interface VarExpiration<K, V> {
534

535
    /**
536
     * Returns the duration until the entry should be automatically removed. The expiration policy
537
     * determines when the entry's duration is reset. In an asynchronous cache while the future is
538
     * incomplete then the duration may be extended into the distant future.
539
     *
540
     * @param key the key for the entry being queried
541
     * @param unit the unit that {@code age} is expressed in
542
     * @return the duration if the entry is present in the cache
543
     * @throws NullPointerException if the specified key or unit is null
544
     */
545
    OptionalLong getExpiresAfter(K key, TimeUnit unit);
546

547
    /**
548
     * Returns the duration until the entry should be automatically removed. The expiration policy
549
     * determines when the entry's duration is reset. In an asynchronous cache while the future is
550
     * incomplete then the duration may be extended into the distant future.
551
     *
552
     * @param key the key for the entry being queried
553
     * @return the duration if the entry is present in the cache
554
     * @throws NullPointerException if the specified key is null
555
     */
556
    default Optional<Duration> getExpiresAfter(K key) {
557
      OptionalLong duration = getExpiresAfter(key, TimeUnit.NANOSECONDS);
1✔
558
      return duration.isPresent()
1✔
559
          ? Optional.of(Duration.ofNanos(duration.getAsLong()))
1✔
560
          : Optional.empty();
1✔
561
    }
562

563
    /**
564
     * Specifies that the entry should be automatically removed from the cache once the duration has
565
     * elapsed. The expiration policy determines when the entry's age is reset. This method has no
566
     * effect if the mapping is absent or, in an asynchronous cache, if the future is incomplete.
567
     *
568
     * @param key the key for the entry being set
569
     * @param duration the length of time from now when the entry should be automatically removed
570
     * @param unit the unit that {@code duration} is expressed in
571
     * @throws IllegalArgumentException if {@code duration} is negative
572
     * @throws NullPointerException if the specified key or unit is null
573
     */
574
    void setExpiresAfter(K key, long duration, TimeUnit unit);
575

576
    /**
577
     * Specifies that the entry should be automatically removed from the cache once the duration has
578
     * elapsed. The expiration policy determines when the entry's age is reset. This method has no
579
     * effect if the mapping is absent or, in an asynchronous cache, if the future is incomplete.
580
     *
581
     * @param key the key for the entry being set
582
     * @param duration the length of time from now when the entry should be automatically removed
583
     * @throws IllegalArgumentException if {@code duration} is negative
584
     * @throws NullPointerException if the specified key or duration is null
585
     */
586
    default void setExpiresAfter(K key, Duration duration) {
587
      setExpiresAfter(key, toNanosSaturated(duration), TimeUnit.NANOSECONDS);
1✔
588
    }
1✔
589

590
    /**
591
     * Associates the {@code value} with the {@code key} in this cache if the specified key is not
592
     * already associated with a value. This method differs from {@link Map#putIfAbsent} by
593
     * substituting the configured {@link Expiry} with the specified duration and has no effect on
594
     * the duration if the entry was present.
595
     *
596
     * @param key the key with which the specified value is to be associated
597
     * @param value value to be associated with the specified key
598
     * @param duration the length of time from now when the entry should be automatically removed
599
     * @param unit the unit that {@code duration} is expressed in
600
     * @return the previous value associated with the specified key, or {@code null} if there was no
601
     *         mapping for the key.
602
     * @throws IllegalArgumentException if {@code duration} is negative
603
     * @throws NullPointerException if the specified key, value, or unit is null
604
     */
605
    @Nullable V putIfAbsent(K key, V value, long duration, TimeUnit unit);
606

607
    /**
608
     * Associates the {@code value} with the {@code key} in this cache if the specified key is not
609
     * already associated with a value. This method differs from {@link Map#putIfAbsent} by
610
     * substituting the configured {@link Expiry} with the specified duration and has no effect on
611
     * the duration if the entry was present.
612
     *
613
     * @param key the key with which the specified value is to be associated
614
     * @param value value to be associated with the specified key
615
     * @param duration the length of time from now when the entry should be automatically removed
616
     * @return the previous value associated with the specified key, or {@code null} if there was no
617
     *         mapping for the key.
618
     * @throws IllegalArgumentException if {@code duration} is negative
619
     * @throws NullPointerException if the specified key, value, or duration is null
620
     */
621
    default @Nullable V putIfAbsent(K key, V value, Duration duration) {
622
      return putIfAbsent(key, value, toNanosSaturated(duration), TimeUnit.NANOSECONDS);
1✔
623
    }
624

625
    /**
626
     * Associates the {@code value} with the {@code key} in this cache. If the cache previously
627
     * contained a value associated with the {@code key}, the old value is replaced by the new
628
     * {@code value}. This method differs from {@link Cache#put} by substituting the configured
629
     * {@link Expiry} with the specified duration.
630
     *
631
     * @param key the key with which the specified value is to be associated
632
     * @param value value to be associated with the specified key
633
     * @param duration the length of time from now when the entry should be automatically removed
634
     * @param unit the unit that {@code duration} is expressed in
635
     * @return the previous value associated with {@code key}, or {@code null} if there was no
636
     *         mapping for {@code key}.
637
     * @throws IllegalArgumentException if {@code duration} is negative
638
     * @throws NullPointerException if the specified key, value, or unit is null
639
     */
640
    @Nullable V put(K key, V value, long duration, TimeUnit unit);
641

642
    /**
643
     * Associates the {@code value} with the {@code key} in this cache. If the cache previously
644
     * contained a value associated with the {@code key}, the old value is replaced by the new
645
     * {@code value}. This method differs from {@link Cache#put} by substituting the
646
     * configured {@link Expiry} with the specified duration.
647
     *
648
     * @param key the key with which the specified value is to be associated
649
     * @param value value to be associated with the specified key
650
     * @param duration the length of time from now when the entry should be automatically removed
651
     * @return the previous value associated with {@code key}, or {@code null} if there was no
652
     *         mapping for {@code key}.
653
     * @throws IllegalArgumentException if {@code duration} is negative
654
     * @throws NullPointerException if the specified key, value, or duration is null
655
     */
656
    default @Nullable V put(K key, V value, Duration duration) {
657
      return put(key, value, toNanosSaturated(duration), TimeUnit.NANOSECONDS);
1✔
658
    }
659

660
    /**
661
     * Attempts to compute a mapping for the specified key and its current mapped value (or
662
     * {@code null} if there is no current mapping). The entire method invocation is performed
663
     * atomically. The supplied function is invoked exactly once per invocation of this method. Some
664
     * attempted update operations on this cache by other threads may be blocked while the
665
     * computation is in progress, so the computation should be short and simple. This method
666
     * differs from {@code cache.asMap().compute(key, remappingFunction)} by substituting the
667
     * configured {@link Expiry} with the specified duration.
668
     * <p>
669
     * <b>Warning:</b> the {@code remappingFunction} <b>must not</b> modify this cache during the
670
     * computation.
671
     *
672
     * @param key key with which the specified value is to be associated
673
     * @param remappingFunction the function to compute a value
674
     * @param duration the length of time from now when the entry should be automatically removed
675
     * @return the new value associated with the specified key, or null if none
676
     * @throws IllegalArgumentException if {@code duration} is negative
677
     * @throws NullPointerException if the specified key, remappingFunction, or duration is null
678
     * @throws IllegalStateException if the computation detectably attempts a recursive update to
679
     *         this cache that would otherwise never complete
680
     * @throws RuntimeException or Error if the remappingFunction does so, in which case the mapping
681
     *         is unchanged
682
     * @since 3.0.6
683
     */
684
    default @Nullable V compute(K key,
685
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction,
686
        Duration duration) {
UNCOV
687
      throw new UnsupportedOperationException();
×
688
    }
689

690
    /**
691
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
692
     * order of iteration is from the entries most likely to expire (oldest) to the entries least
693
     * likely to expire (youngest). This order is determined by the expiration policy's best guess
694
     * at the time of creating this snapshot view.
695
     * <p>
696
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
697
     * asynchronous nature of the page replacement policy, determining the retention ordering
698
     * requires a traversal of the entries.
699
     *
700
     * @param limit the maximum size of the returned map (use {@link Integer#MAX_VALUE} to disregard
701
     *        the limit)
702
     * @return a snapshot view of the cache from the oldest entry to the youngest
703
     * @throws IllegalArgumentException if the limit specified is negative
704
     */
705
    Map<K, V> oldest(int limit);
706

707
    /**
708
     * Returns the computed result from the ordered traversal of the cache entries. The order of
709
     * iteration is from the entries most likely to expire (oldest) to the entries least likely to
710
     * expire (youngest). This order is determined by the expiration policy's best guess at the time
711
     * of creating this computation.
712
     * <p>
713
     * Usage example:
714
     * {@snippet class=com.github.benmanes.caffeine.cache.Snippets
715
     *           region=expireVar_oldest lang=java}
716
     * <p>
717
     * Beware that this computation is performed within the eviction policy's exclusive lock, so the
718
     * computation should be short and simple. While the computation is in progress further eviction
719
     * maintenance will be halted.
720
     *
721
     * @param <T> the type of the result of the mappingFunction
722
     * @param mappingFunction the mapping function to compute a value
723
     * @return the computed value
724
     * @throws NullPointerException if the mappingFunction is null
725
     * @throws RuntimeException or Error if the mappingFunction does so
726
     * @throws java.util.ConcurrentModificationException if the computation detectably reads or
727
     *         writes an entry in this cache
728
     * @since 3.0.6
729
     */
730
    @SuppressWarnings({"JavadocDeclaration", "JavadocReference"})
731
    default <T extends @Nullable Object> T oldest(
732
        Function<Stream<CacheEntry<K, V>>, T> mappingFunction) {
UNCOV
733
      throw new UnsupportedOperationException();
×
734
    }
735

736
    /**
737
     * Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
738
     * order of iteration is from the entries least likely to expire (youngest) to the entries most
739
     * likely to expire (oldest). This order is determined by the expiration policy's best guess at
740
     * the time of creating this snapshot view.
741
     * <p>
742
     * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
743
     * asynchronous nature of the page replacement policy, determining the retention ordering
744
     * requires a traversal of the entries.
745
     *
746
     * @param limit the maximum size of the returned map (use {@link Integer#MAX_VALUE} to disregard
747
     *        the limit)
748
     * @return a snapshot view of the cache from the youngest entry to the oldest
749
     * @throws IllegalArgumentException if the limit specified is negative
750
     */
751
    Map<K, V> youngest(int limit);
752

753
    /**
754
     * Returns the computed result from the ordered traversal of the cache entries. The order of
755
     * iteration is from the entries least likely to expire (youngest) to the entries most likely to
756
     * expire (oldest). This order is determined by the expiration policy's best guess at the time
757
     * of creating this computation.
758
     * <p>
759
     * Usage example:
760
     * {@snippet class=com.github.benmanes.caffeine.cache.Snippets
761
     *           region=expireVar_youngest lang=java}
762
     * <p>
763
     * Beware that this computation is performed within the eviction policy's exclusive lock, so the
764
     * computation should be short and simple. While the computation is in progress further eviction
765
     * maintenance will be halted.
766
     *
767
     * @param <T> the type of the result of the mappingFunction
768
     * @param mappingFunction the mapping function to compute a value
769
     * @return the computed value
770
     * @throws NullPointerException if the mappingFunction is null
771
     * @throws RuntimeException or Error if the mappingFunction does so
772
     * @throws java.util.ConcurrentModificationException if the computation detectably reads or
773
     *         writes an entry in this cache
774
     * @since 3.0.6
775
     */
776
    @SuppressWarnings({"JavadocDeclaration", "JavadocReference"})
777
    default <T extends @Nullable Object> T youngest(
778
        Function<Stream<CacheEntry<K, V>>, T> mappingFunction) {
UNCOV
779
      throw new UnsupportedOperationException();
×
780
    }
781
  }
782

783
  /**
784
   * The low-level operations for a cache with a fixed refresh policy.
785
   *
786
   * @param <K> the type of keys
787
   * @param <V> the type of values
788
   */
789
  interface FixedRefresh<K, V> {
790

791
    /**
792
     * Returns the age of the entry based on the refresh policy. The entry's age is the cache's
793
     * estimate of the amount of time since the entry's refresh period was last reset. In an
794
     * asynchronous cache while the future is incomplete then the duration may be negative.
795
     * <p>
796
     * A refresh policy uses the age to determine if an entry is fresh or stale by comparing it
797
     * to the freshness lifetime. This is calculated as {@code fresh = freshnessLifetime > age}
798
     * where {@code freshnessLifetime = expires - currentTime}.
799
     *
800
     * @param key the key for the entry being queried
801
     * @param unit the unit that {@code age} is expressed in
802
     * @return the age if the entry is present in the cache
803
     * @throws NullPointerException if the specified key or unit is null
804
     */
805
    OptionalLong ageOf(K key, TimeUnit unit);
806

807
    /**
808
     * Returns the age of the entry based on the refresh policy. The entry's age is the cache's
809
     * estimate of the amount of time since the entry's refresh period was last reset. In an
810
     * asynchronous cache while the future is incomplete then the duration may be negative.
811
     * <p>
812
     * A refresh policy uses the age to determine if an entry is fresh or stale by comparing it
813
     * to the freshness lifetime. This is calculated as {@code fresh = freshnessLifetime > age}
814
     * where {@code freshnessLifetime = expires - currentTime}.
815
     *
816
     * @param key the key for the entry being queried
817
     * @return the age if the entry is present in the cache
818
     * @throws NullPointerException if the specified key is null
819
     */
820
    default Optional<Duration> ageOf(K key) {
821
      OptionalLong duration = ageOf(key, TimeUnit.NANOSECONDS);
1✔
822
      return duration.isPresent()
1✔
823
          ? Optional.of(Duration.ofNanos(duration.getAsLong()))
1✔
824
          : Optional.empty();
1✔
825
    }
826

827
    /**
828
     * Returns the fixed duration used to determine if an entry should be eligible for reloading due
829
     * to elapsing this time bound. An entry is considered fresh if its age is less than this
830
     * duration, and stale otherwise. The refresh policy determines when the entry's age is
831
     * reset.
832
     *
833
     * @param unit the unit that duration is expressed in
834
     * @return the length of time after which an entry is eligible to be reloaded
835
     * @throws NullPointerException if the unit is null
836
     */
837
    long getRefreshesAfter(TimeUnit unit);
838

839
    /**
840
     * Returns the fixed duration used to determine if an entry should be eligible for reloading due
841
     * to elapsing this time bound. An entry is considered fresh if its age is less than this
842
     * duration, and stale otherwise. The refresh policy determines when the entry's age is
843
     * reset.
844
     *
845
     * @return the length of time after which an entry is eligible to be reloaded
846
     */
847
    default Duration getRefreshesAfter() {
848
      return Duration.ofNanos(getRefreshesAfter(TimeUnit.NANOSECONDS));
1✔
849
    }
850

851
    /**
852
     * Specifies that each entry should be eligible for reloading once a fixed duration has elapsed.
853
     * The refresh policy determines when the entry's age is reset.
854
     *
855
     * @param duration the length of time after which an entry is eligible to be reloaded
856
     * @param unit the unit that {@code duration} is expressed in
857
     * @throws IllegalArgumentException if {@code duration} is zero or negative
858
     * @throws NullPointerException if the unit is null
859
     */
860
    void setRefreshesAfter(long duration, TimeUnit unit);
861

862
    /**
863
     * Specifies that each entry should be eligible for reloading once a fixed duration has elapsed.
864
     * The refresh policy determines when the entry's age is reset.
865
     *
866
     * @param duration the length of time after which an entry is eligible to be reloaded
867
     * @throws IllegalArgumentException if {@code duration} is zero or negative
868
     * @throws NullPointerException if the duration is null
869
     */
870
    default void setRefreshesAfter(Duration duration) {
871
      setRefreshesAfter(toNanosSaturated(duration), TimeUnit.NANOSECONDS);
1✔
872
    }
1✔
873
  }
874

875
  /**
876
   * A key-value pair that may include policy metadata for the cached entry. Unless otherwise
877
   * specified, this is a value-based class, it can be assumed that the implementation is an
878
   * immutable snapshot of the cached data at the time of this entry's creation, and it will not
879
   * reflect changes afterward.
880
   *
881
   * @param <K> the type of keys
882
   * @param <V> the type of values
883
   */
884
  @NullMarked
885
  interface CacheEntry<K, V> extends Map.Entry<K, V> {
886

887
    /**
888
     * Returns the entry's weight. If the cache was not configured with a maximum weight then this
889
     * value is always {@code 1}.
890
     *
891
     * @return the weight if the entry
892
     */
893
    int weight();
894

895
    /**
896
     * Returns the {@link Ticker#read()} ticks for when this entry expires. If the cache was not
897
     * configured with an expiration policy then this value is roughly {@link Long#MAX_VALUE}
898
     * ticks away from the {@link #snapshotAt()} reading.
899
     *
900
     * @return the ticker reading for when the entry expires
901
     */
902
    long expiresAt();
903

904
    /**
905
     * Returns the duration between {@link #expiresAt()} and {@link #snapshotAt()}.
906
     *
907
     * @return the length of time after which the entry will be automatically removed
908
     */
909
    default Duration expiresAfter() {
910
      return Duration.ofNanos(expiresAt() - snapshotAt());
1✔
911
    }
912

913
    /**
914
     * Returns the {@link Ticker#read()} ticks for when this entry becomes refreshable. If the cache
915
     * was not configured with a refresh policy then this value is roughly {@link Long#MAX_VALUE}
916
     * ticks away from the {@link #snapshotAt()} reading.
917
     *
918
     * @return the ticker reading for when the entry may be refreshed
919
     */
920
    long refreshableAt();
921

922
    /**
923
     * Returns the duration between {@link #refreshableAt()} and {@link #snapshotAt()}.
924
     *
925
     * @return the length of time after which an entry is eligible to be reloaded
926
     */
927
    default Duration refreshableAfter() {
928
      return Duration.ofNanos(refreshableAt() - snapshotAt());
1✔
929
    }
930

931
    /**
932
     * Returns the {@link Ticker#read()} ticks for when this snapshot of the entry was taken. This
933
     * reading may be a constant if no time-based policy is configured.
934
     *
935
     * @return the ticker reading for when this snapshot of the entry was taken.
936
     */
937
    long snapshotAt();
938
  }
939
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc