diff --git a/.gitignore b/.gitignore
index 8f6fc3c8dd..e3764d839f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ jitwatch.out
 .settings
 .project
 .gradle
+.kotlin
 .vscode
 .idea
 *.iml
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java
index 3d5d2314f2..97ff1a6a14 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java
@@ -3163,6 +3163,7 @@ <T> T expireAfterAccessOrder(boolean oldest, Function<@Nullable V, @Nullable V>
    * @param mappingFunction the mapping function to compute a value
    * @return the computed value
    */
+  @SuppressWarnings("NullAway")
   <T> T snapshot(Iterable<Node<K, V>> iterable, Function<@Nullable V, @Nullable V> transformer,
       Function<Stream<CacheEntry<K, V>>, T> mappingFunction) {
     requireNonNull(mappingFunction);
@@ -3901,10 +3902,10 @@ public void run() {
      */
     // public final void quietlyComplete() {}
 
-    @Override public void setRawResult(@Nullable Void v) {}
     @Override public void complete(@Nullable Void value) {}
-    @Override public void completeExceptionally(Throwable ex) {}
+    @Override public void setRawResult(@Nullable Void value) {}
     @Override public @Nullable Void getRawResult() { return null; }
+    @Override public void completeExceptionally(@Nullable Throwable t) {}
     @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; }
   }
 
@@ -4009,6 +4010,7 @@ static final class BoundedPolicy<K, V> implements Policy<K, V> {
     @Override public @Nullable V getIfPresentQuietly(K key) {
       return transformer.apply(cache.getIfPresentQuietly(key));
     }
+    @SuppressWarnings("NullAway")
     @Override public @Nullable CacheEntry<K, V> getEntryIfPresentQuietly(K key) {
       Node<K, V> node = cache.data.get(cache.nodeFactory.newLookupKey(key));
       return (node == null) ? null : cache.nodeToCacheEntry(node, transformer);
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LinkedDeque.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LinkedDeque.java
index 9c3fed30df..d0c4ab1b8a 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LinkedDeque.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LinkedDeque.java
@@ -55,14 +55,14 @@ interface LinkedDeque<E> extends Deque<E> {
    *
    * @param e the linked element
    */
-  boolean isFirst(E e);
+  boolean isFirst(@Nullable E e);
 
   /**
    * Returns if the element is at the back of the deque.
    *
    * @param e the linked element
    */
-  boolean isLast(E e);
+  boolean isLast(@Nullable E e);
 
   /**
    * Moves the element to the front of the deque so that it becomes the first element.
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java
index 9b308fe6be..9466536d18 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java
@@ -28,6 +28,7 @@
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -1841,7 +1842,9 @@ public void keySet_removeAll_partial(Map<Int, Int> map, CacheContext context) {
     assertThat(map.keySet().removeAll(context.firstMiddleLastKeys())).isTrue();
     assertThat(map).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
@@ -2017,7 +2020,9 @@ public void keySet_retainAll_partial(Map<Int, Int> map, CacheContext context) {
     assertThat(map.keySet().retainAll(expected.keySet())).isTrue();
     assertThat(map).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
@@ -2149,6 +2154,7 @@ public void keySpliterator_estimateSize(Map<Int, Int> map, CacheContext context)
   /* --------------- Values --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void values_toArray_null(Map<Int, Int> map, CacheContext context) {
@@ -2249,7 +2255,8 @@ public void values_removeAll_none_populated(Map<Int, Int> map, CacheContext cont
   public void values_removeAll_partial(Map<Int, Int> map, CacheContext context) {
     var expected = new HashMap<>(context.original());
     expected.keySet().removeAll(context.firstMiddleLastKeys());
-    var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get);
+    var removed = Maps.toMap(context.firstMiddleLastKeys(),
+        key -> requireNonNull(context.original().get(key)));
 
     assertThat(map.values().removeAll(removed.values())).isTrue();
     assertThat(map).isEqualTo(expected);
@@ -2418,7 +2425,8 @@ public void values_retainAll_none_populated(Map<Int, Int> map, CacheContext cont
   public void values_retainAll_partial(Map<Int, Int> map, CacheContext context) {
     var expected = new HashMap<>(context.original());
     expected.keySet().removeAll(context.firstMiddleLastKeys());
-    var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get);
+    var removed = Maps.toMap(context.firstMiddleLastKeys(),
+        key -> requireNonNull(context.original().get(key)));
 
     assertThat(map.values().retainAll(expected.values())).isTrue();
     assertThat(map).isEqualTo(expected);
@@ -2678,7 +2686,8 @@ public void entrySet_removeAll_none_populated(Map<Int, Int> map, CacheContext co
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL)
   public void entrySet_removeAll_partial(Map<Int, Int> map, CacheContext context) {
-    var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get);
+    var removed = Maps.toMap(context.firstMiddleLastKeys(),
+        key -> requireNonNull(context.original().get(key)));
     var expected = new HashMap<>(context.original());
     expected.entrySet().removeAll(removed.entrySet());
 
@@ -2888,7 +2897,8 @@ public void entrySet_retainAll_none_populated(Map<Int, Int> map, CacheContext co
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL)
   public void entrySet_retainAll_partial(Map<Int, Int> map, CacheContext context) {
-    var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get);
+    var removed = Maps.toMap(context.firstMiddleLastKeys(),
+        key -> requireNonNull(context.original().get(key)));
     var expected = new HashMap<>(context.original());
     expected.entrySet().removeAll(removed.entrySet());
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java
index 57dd96cb68..c4c0534b4b 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java
@@ -28,6 +28,7 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -292,8 +293,8 @@ public void put_insert(AsyncCache<Int, Int> cache, CacheContext context) {
   public void put_replace_sameValue(AsyncCache<Int, Int> cache, CacheContext context) {
     var replaced = new HashMap<Int, Int>();
     for (Int key : context.firstMiddleLastKeys()) {
-      var oldValue = cache.asMap().get(key);
-      var value = cache.asMap().get(key).thenApply(val -> intern(new Int(val)));
+      var oldValue = requireNonNull(cache.asMap().get(key));
+      var value = oldValue.thenApply(val -> intern(new Int(val)));
       assertThat(cache.asMap().put(key, value)).isSameInstanceAs(oldValue);
       assertThat(cache).containsEntry(key, value);
       replaced.put(key, context.original().get(key));
@@ -307,7 +308,7 @@ public void put_replace_sameValue(AsyncCache<Int, Int> cache, CacheContext conte
   @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL })
   public void put_replace_sameInstance(AsyncCache<Int, Int> cache, CacheContext context) {
     for (Int key : context.firstMiddleLastKeys()) {
-      var value = cache.asMap().get(key);
+      var value = requireNonNull(cache.asMap().get(key));
       assertThat(cache.asMap().put(key, value)).isSameInstanceAs(value);
       assertThat(cache).containsEntry(key, value);
     }
@@ -435,7 +436,7 @@ public void putIfAbsent_nullKeyAndValue(AsyncCache<Int, Int> cache, CacheContext
   removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void putIfAbsent_present(AsyncCache<Int, Int> cache, CacheContext context) {
     for (Int key : context.firstMiddleLastKeys()) {
-      var value = cache.asMap().get(key);
+      var value = requireNonNull(cache.asMap().get(key));
       assertThat(cache.asMap().putIfAbsent(key, key.asFuture())).isEqualTo(value);
       assertThat(cache).containsEntry(key, value);
     }
@@ -589,8 +590,8 @@ public void replace_failure(AsyncCache<Int, Int> cache, CacheContext context) {
   public void replace_sameValue(AsyncCache<Int, Int> cache, CacheContext context) {
     var replaced = new HashMap<Int, Int>();
     for (Int key : context.firstMiddleLastKeys()) {
-      var oldValue = cache.asMap().get(key);
-      var newValue = cache.asMap().get(key).thenApply(val -> intern(new Int(val)));
+      var oldValue = requireNonNull(cache.asMap().get(key));
+      var newValue = oldValue.thenApply(val -> intern(new Int(val)));
       assertThat(cache.asMap().replace(key, newValue)).isSameInstanceAs(oldValue);
       assertThat(cache).containsEntry(key, newValue);
       replaced.put(key, context.original().get(key));
@@ -718,8 +719,8 @@ public void replaceConditionally_wrongOldValue(AsyncCache<Int, Int> cache, Cache
   public void replaceConditionally_sameValue(AsyncCache<Int, Int> cache, CacheContext context) {
     var replaced = new HashMap<Int, Int>();
     for (Int key : context.firstMiddleLastKeys()) {
-      var oldValue = cache.asMap().get(key);
-      var newValue = cache.asMap().get(key).thenApply(val -> intern(new Int(val)));
+      var oldValue = requireNonNull(cache.asMap().get(key));
+      var newValue = oldValue.thenApply(val -> intern(new Int(val)));
       assertThat(cache.asMap().replace(key, oldValue, newValue)).isTrue();
       assertThat(cache).containsEntry(key, newValue);
       replaced.put(key, context.original().get(key));
@@ -1310,7 +1311,7 @@ public void merge_absent(AsyncCache<Int, Int> cache, CacheContext context) {
   public void merge_sameValue(AsyncCache<Int, Int> cache, CacheContext context) {
     var replaced = new HashMap<Int, CompletableFuture<Int>>();
     for (Int key : context.firstMiddleLastKeys()) {
-      var value = cache.asMap().get(key).thenApply(Int::new);
+      var value = requireNonNull(cache.asMap().get(key)).thenApply(Int::new);
       var result = cache.asMap().merge(key, key.negate().asFuture(), (oldValue, v) -> value);
       assertThat(result).isSameInstanceAs(value);
       replaced.put(key, value);
@@ -1391,12 +1392,12 @@ public void equals(AsyncCache<Int, Int> cache, CacheContext context) {
     assertThat(cache.asMap().equals(map)).isTrue();
     assertThat(map.equals(cache.asMap())).isTrue();
 
-    var absent = Maps.asMap(context.absentKeys(), CompletableFuture::completedFuture);
+    var absent = Maps.toMap(context.absentKeys(), CompletableFuture::completedFuture);
     assertThat(cache.asMap().equals(absent)).isFalse();
     assertThat(absent.equals(cache.asMap())).isFalse();
 
     if (!cache.asMap().isEmpty()) {
-      var other = Maps.asMap(cache.asMap().keySet(), CompletableFuture::completedFuture);
+      var other = Maps.toMap(cache.asMap().keySet(), CompletableFuture::completedFuture);
       assertThat(cache.asMap().equals(other)).isFalse();
       assertThat(other.equals(cache.asMap())).isFalse();
     }
@@ -1566,7 +1567,9 @@ public void keySet_removeAll_partial(AsyncCache<Int, Int> cache, CacheContext co
     assertThat(cache.asMap().keySet().removeAll(context.firstMiddleLastKeys())).isTrue();
     assertThat(cache.synchronous().asMap()).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
@@ -1739,7 +1742,9 @@ public void keySet_retainAll_partial(AsyncCache<Int, Int> cache, CacheContext co
     assertThat(cache.asMap().keySet().retainAll(expected.keySet())).isTrue();
     assertThat(cache.asMap()).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
@@ -1872,6 +1877,7 @@ public void keySpliterator_estimateSize(AsyncCache<Int, Int> cache, CacheContext
   /* ---------------- Values -------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void values_toArray_null(AsyncCache<Int, Int> cache, CacheContext context) {
@@ -1978,12 +1984,15 @@ public void values_removeAll_none_populated(AsyncCache<Int, Int> cache, CacheCon
   public void values_removeAll_partial(AsyncCache<Int, Int> cache, CacheContext context) {
     var expected = new HashMap<>(context.original());
     expected.keySet().removeAll(context.firstMiddleLastKeys());
-    var removed = Maps.asMap(context.firstMiddleLastKeys(), cache.asMap()::get);
+    var removed = Maps.toMap(context.firstMiddleLastKeys(),
+        key -> requireNonNull(cache.asMap().get(key)));
 
     assertThat(cache.asMap().values().removeAll(removed.values())).isTrue();
     assertThat(cache.synchronous().asMap()).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
@@ -2026,7 +2035,7 @@ public void values_remove_none(AsyncCache<Int, Int> cache, CacheContext context)
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL)
   public void values_remove(AsyncCache<Int, Int> cache, CacheContext context) {
-    var future = cache.asMap().get(context.firstKey());
+    var future = requireNonNull(cache.asMap().get(context.firstKey()));
     assertThat(cache.asMap().values().remove(future)).isTrue();
     var expected = new HashMap<>(context.original());
     expected.remove(context.firstKey());
@@ -2167,7 +2176,9 @@ public void values_retainAll_partial(AsyncCache<Int, Int> cache, CacheContext co
     assertThat(cache.asMap().values().retainAll(expected.values())).isTrue();
     assertThat(cache.asMap()).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
@@ -2438,14 +2449,17 @@ public void entrySet_removeAll_none_populated(AsyncCache<Int, Int> cache, CacheC
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL)
   public void entrySet_removeAll_partial(AsyncCache<Int, Int> cache, CacheContext context) {
-    var removed = Maps.asMap(context.firstMiddleLastKeys(), cache.asMap()::get);
+    var removed = Maps.toMap(context.firstMiddleLastKeys(),
+        key -> requireNonNull(cache.asMap().get(key)));
     var expected = new HashMap<>(context.original());
     expected.keySet().removeAll(removed.keySet());
 
     assertThat(cache.asMap().entrySet().removeAll(removed.entrySet())).isTrue();
     assertThat(cache.synchronous().asMap()).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
@@ -2656,7 +2670,9 @@ public void entrySet_retainAll_partial(AsyncCache<Int, Int> cache, CacheContext
     assertThat(cache.asMap().entrySet().retainAll(expected.entrySet())).isTrue();
     assertThat(cache.asMap()).isEqualTo(expected);
     assertThat(context).removalNotifications().withCause(EXPLICIT)
-        .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively();
+        .contains(Maps.toMap(context.firstMiddleLastKeys(),
+            key -> requireNonNull(context.original().get(key))))
+        .exclusively();
   }
 
   @CheckNoStats
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java
index 908b1b6157..6a0cf023a6 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java
@@ -29,6 +29,7 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -85,6 +86,7 @@ public final class AsyncCacheTest {
 
   /* --------------- getIfPresent --------------- */
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getIfPresent_nullKey(AsyncCache<Int, Int> cache, CacheContext context) {
@@ -111,12 +113,14 @@ public void getIfPresent_present(AsyncCache<Int, Int> cache, CacheContext contex
   /* --------------- getFunc --------------- */
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void getFunc_nullKey(AsyncCache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.get(null, key -> null));
   }
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void getFunc_nullLoader(AsyncCache<Int, Int> cache, CacheContext context) {
     Function<Int, Int> mappingFunction = null;
@@ -124,6 +128,7 @@ public void getFunc_nullLoader(AsyncCache<Int, Int> cache, CacheContext context)
   }
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void getFunc_nullKeyAndLoader(AsyncCache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.get(null, (Function<Int, Int>) null));
@@ -133,6 +138,7 @@ public void getFunc_nullKeyAndLoader(AsyncCache<Int, Int> cache, CacheContext co
   @Test(dataProvider = "caches")
   public void getFunc_absent_null(AsyncCache<Int, Int> cache, CacheContext context) {
     Int key = context.absentKey();
+    @SuppressWarnings("NullAway")
     var valueFuture = cache.get(key, k -> null);
     assertThat(context).stats().hits(0).misses(1).success(0).failures(1);
 
@@ -146,6 +152,7 @@ public void getFunc_absent_null_async(AsyncCache<Int, Int> cache, CacheContext c
     Int key = context.absentKey();
     var ready = new AtomicBoolean();
     var done = new AtomicBoolean();
+    @SuppressWarnings("NullAway")
     var valueFuture = cache.get(key, k -> {
       await().untilTrue(ready);
       return null;
@@ -201,6 +208,7 @@ public void getFunc_absent_failure_async(AsyncCache<Int, Int> cache, CacheContex
   @CacheSpec(executor = CacheExecutor.THREADED, executorFailure = ExecutorFailure.IGNORED)
   public void getFunc_absent_cancelled(AsyncCache<Int, Int> cache, CacheContext context) {
     var done = new AtomicBoolean();
+    @SuppressWarnings("NullAway")
     var valueFuture = cache.get(context.absentKey(), k -> {
       await().until(done::get);
       return null;
@@ -241,6 +249,7 @@ public void getFunc_present(AsyncCache<Int, Int> cache, CacheContext context) {
   /* --------------- getBiFunc --------------- */
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void getBiFunc_nullKey(AsyncCache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () ->
@@ -248,6 +257,7 @@ public void getBiFunc_nullKey(AsyncCache<Int, Int> cache, CacheContext context)
   }
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void getBiFunc_nullLoader(AsyncCache<Int, Int> cache, CacheContext context) {
     BiFunction<Int, Executor, CompletableFuture<Int>> mappingFunction = null;
@@ -255,6 +265,7 @@ public void getBiFunc_nullLoader(AsyncCache<Int, Int> cache, CacheContext contex
   }
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void getBiFunc_nullKeyAndLoader(AsyncCache<Int, Int> cache, CacheContext context) {
     BiFunction<Int, Executor, CompletableFuture<Int>> mappingFunction = null;
@@ -372,6 +383,7 @@ public void getBiFunc_present(AsyncCache<Int, Int> cache, CacheContext context)
   /* --------------- getAllFunc --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllFunction_nullKeys(AsyncCache<Int, Int> cache, CacheContext context) {
@@ -380,6 +392,7 @@ public void getAllFunction_nullKeys(AsyncCache<Int, Int> cache, CacheContext con
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllFunction_nullKeys_nullFunction(
@@ -389,6 +402,7 @@ public void getAllFunction_nullKeys_nullFunction(
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllFunction_nullFunction(AsyncCache<Int, Int> cache, CacheContext context) {
@@ -413,11 +427,12 @@ public void getAllFunction_iterable_empty(AsyncCache<Int, Int> cache, CacheConte
     assertThat(result).isExhaustivelyEmpty();
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllFunction_nullLookup(AsyncCache<Int, Int> cache, CacheContext context) {
     var result = cache.getAll(context.firstMiddleLastKeys(),
-        keys -> Maps.asMap(keys, Int::negate)).join();
+        keys -> Maps.toMap(keys, Int::negate)).join();
     assertThat(result.containsValue(null)).isFalse();
     assertThat(result.containsKey(null)).isFalse();
     assertThat(result.get(null)).isNull();
@@ -443,6 +458,7 @@ public void getAllFunction_immutable_result(AsyncCache<Int, Int> cache, CacheCon
   }
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void getAllFunction_absent_null(AsyncCache<Int, Int> cache, CacheContext context) {
     assertThat(cache.getAll(context.absentKeys(), keys -> null))
@@ -609,6 +625,7 @@ public void getAllFunction_canceled_individual(AsyncCache<Int, Int> cache, Cache
     });
     for (var key : context.absentKeys()) {
       var future = cache.getIfPresent(key);
+      requireNonNull(future);
       future.cancel(true);
       assertThat(future).hasCompletedExceptionally();
     }
@@ -655,6 +672,7 @@ public void getAllFunction_badLoader(AsyncCache<Int, Int> cache, CacheContext co
   /* --------------- getAllBiFunc --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllBifunction_nullKeys(AsyncCache<Int, Int> cache, CacheContext context) {
@@ -663,6 +681,7 @@ public void getAllBifunction_nullKeys(AsyncCache<Int, Int> cache, CacheContext c
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllBifunction_nullKeys_nullBifunction(
@@ -672,6 +691,7 @@ public void getAllBifunction_nullKeys_nullBifunction(
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllBifunction_nullBifunction(AsyncCache<Int, Int> cache, CacheContext context) {
@@ -702,7 +722,7 @@ public void getAllBifunction_iterable_empty(AsyncCache<Int, Int> cache, CacheCon
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllBiFunction_nullLookup(AsyncCache<Int, Int> cache, CacheContext context) {
     var result = cache.getAll(context.firstMiddleLastKeys(), (keys, executor) ->
-        CompletableFuture.completedFuture(Maps.asMap(keys, Int::negate))).join();
+        CompletableFuture.completedFuture(Maps.toMap(keys, Int::negate))).join();
     assertThat(result.containsValue(null)).isFalse();
     assertThat(result.containsKey(null)).isFalse();
     assertThat(result.get(null)).isNull();
@@ -938,6 +958,7 @@ public void getAllBifunction_canceled_individual(
     });
     for (var key : context.absentKeys()) {
       var future = cache.getIfPresent(key);
+      requireNonNull(future);
       future.cancel(true);
       assertThat(future).hasCompletedExceptionally();
     }
@@ -1010,6 +1031,7 @@ public void getAllBifunction_early_success(AsyncCache<Int, Int> cache, CacheCont
     var result = cache.getAll(context.absentKeys(), (keysToLoad, executor) -> bulk);
     var future = cache.asMap().get(key);
 
+    requireNonNull(future);
     future.complete(value);
     bulk.complete(context.absent()); // obtrudes the future's value
 
@@ -1027,6 +1049,8 @@ public void getAllBifunction_early_failure(AsyncCache<Int, Int> cache, CacheCont
     var bulk = new CompletableFuture<Map<Int, Int>>();
     var result = cache.getAll(context.absentKeys(), (keysToLoad, executor) -> bulk);
     var future = cache.asMap().get(key);
+
+    requireNonNull(future);
     future.completeExceptionally(error);
 
     bulk.complete(context.absent());
@@ -1043,6 +1067,7 @@ public void getAllBifunction_early_failure(AsyncCache<Int, Int> cache, CacheCont
 
   /* --------------- put --------------- */
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void put_nullKey(AsyncCache<Int, Int> cache, CacheContext context) {
@@ -1050,12 +1075,14 @@ public void put_nullKey(AsyncCache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.put(null, value));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void put_nullValue(AsyncCache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.put(context.absentKey(), null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void put_nullKeyAndValue(AsyncCache<Int, Int> cache, CacheContext context) {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java
index 70a6fdc282..a0f20e2b84 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java
@@ -27,6 +27,7 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.slf4j.event.Level.WARN;
@@ -84,6 +85,7 @@ public final class AsyncLoadingCacheTest {
   /* --------------- get --------------- */
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void get_null(AsyncLoadingCache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.get(null));
@@ -163,6 +165,7 @@ public void get_present(AsyncLoadingCache<Int, Int> cache, CacheContext context)
   /* --------------- getAll --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAll_iterable_null(AsyncLoadingCache<Int, Int> cache, CacheContext context) {
@@ -428,7 +431,7 @@ public void getAll_canceled_individual(CacheContext context) {
     var cache = context.buildAsync(loader);
     var bulk = cache.getAll(context.absentKeys());
     for (var key : context.absentKeys()) {
-      var future = cache.getIfPresent(key);
+      var future = requireNonNull(cache.getIfPresent(key));
       future.cancel(true);
       assertThat(future).hasCompletedExceptionally();
     }
@@ -537,6 +540,7 @@ public void refresh(CacheContext context) {
     await().untilAsserted(() -> assertThat(cache).containsEntry(key, key.negate()));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.EMPTY, compute = Compute.ASYNC)
   public void refresh_nullFuture_load(CacheContext context) {
@@ -552,6 +556,7 @@ public void refresh_nullFuture_reload(CacheContext context) {
       @Override public CompletableFuture<Int> asyncLoad(Int key, Executor executor) {
         throw new IllegalStateException();
       }
+      @SuppressWarnings("NullAway")
       @Override public CompletableFuture<Int> asyncReload(
           Int key, Int oldValue, Executor executor) {
         return null;
@@ -687,6 +692,7 @@ public void asyncReload() throws Exception {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void bulk_function_null() {
     Function<Set<? extends Int>, Map<Int, Int>> f = null;
     assertThrows(NullPointerException.class, () -> AsyncCacheLoader.bulk(f));
@@ -710,6 +716,7 @@ public void bulk_function_present() throws Exception {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void bulk_bifunction_null() {
     BiFunction<Set<? extends Int>, Executor, CompletableFuture<Map<Int, Int>>> f = null;
     assertThrows(NullPointerException.class, () -> AsyncCacheLoader.bulk(f));
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedBufferTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedBufferTest.java
index ebc45b5bf0..ecfd7838e8 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedBufferTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedBufferTest.java
@@ -83,6 +83,7 @@ public void offerAndDrain(BoundedBuffer<Boolean> buffer) {
 
   @Test
   public void overflow() {
+    @SuppressWarnings("NullAway")
     var buffer = new BoundedBuffer.RingBuffer<Boolean>(null);
     buffer.writeCounter = Long.MAX_VALUE;
     buffer.readCounter = Long.MAX_VALUE;
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java
index 42d54382ab..31c7140109 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java
@@ -46,6 +46,7 @@
 import static java.lang.Thread.State.BLOCKED;
 import static java.lang.Thread.State.WAITING;
 import static java.util.Locale.US;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
@@ -90,8 +91,11 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import org.apache.commons.lang3.mutable.MutableInt;
+import org.jspecify.annotations.NullUnmarked;
+import org.jspecify.annotations.Nullable;
 import org.mockito.Mockito;
 import org.mockito.stubbing.Answer;
 import org.testng.annotations.Listeners;
@@ -140,6 +144,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Range;
+import com.google.common.collect.Streams;
 import com.google.common.testing.GcFinalization;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.Uninterruptibles;
@@ -336,13 +341,11 @@ public void scheduleDrainBuffers() {
 
   @Test
   public void rescheduleDrainBuffers() {
-    var evicting = new AtomicBoolean();
     var done = new AtomicBoolean();
-    var evictionListener = new RemovalListener<Int, Int>() {
-      @Override public void onRemoval(Int key, Int value, RemovalCause cause) {
-        evicting.set(true);
-        await().untilTrue(done);
-      }
+    var evicting = new AtomicBoolean();
+    RemovalListener<Int, Int> evictionListener = (key, value, cause) -> {
+      evicting.set(true);
+      await().untilTrue(done);
     };
     var cache = asBoundedLocalCache(Caffeine.newBuilder()
         .executor(CacheExecutor.THREADED.create())
@@ -428,7 +431,8 @@ public void rescheduleCleanUpIfIncomplete_notScheduled_future(
       BoundedLocalCache<Int, Int> cache, CacheContext context) {
     reset(context.scheduler());
     cache.drainStatus = REQUIRED;
-    cache.pacer().future = new CompletableFuture<>();
+    var pacer = requireNonNull(cache.pacer());
+    pacer.future = new CompletableFuture<>();
 
     cache.rescheduleCleanUpIfIncomplete();
     verifyNoInteractions(context.scheduler());
@@ -441,7 +445,8 @@ public void rescheduleCleanUpIfIncomplete_notScheduled_locked(
       BoundedLocalCache<Int, Int> cache, CacheContext context) {
     reset(context.scheduler());
     cache.drainStatus = REQUIRED;
-    cache.pacer().cancel();
+    var pacer = requireNonNull(cache.pacer());
+    pacer.cancel();
 
     var done = new AtomicBoolean();
     cache.evictionLock.lock();
@@ -467,7 +472,8 @@ public void rescheduleCleanUpIfIncomplete_scheduled_noFuture(
     when(context.scheduler().schedule(any(), any(), anyLong(), any()))
         .thenReturn(new CompletableFuture<>());
     cache.drainStatus = REQUIRED;
-    cache.pacer().cancel();
+    var pacer = requireNonNull(cache.pacer());
+    pacer.cancel();
 
     cache.rescheduleCleanUpIfIncomplete();
     assertThat(cache.pacer().isScheduled()).isTrue();
@@ -482,7 +488,8 @@ public void rescheduleCleanUpIfIncomplete_scheduled_doneFuture(
 
     when(context.scheduler().schedule(any(), any(), anyLong(), any()))
         .thenReturn(new CompletableFuture<>());
-    cache.pacer().future = DisabledFuture.INSTANCE;
+    var pacer = requireNonNull(cache.pacer());
+    pacer.future = DisabledFuture.instance();
     cache.drainStatus = REQUIRED;
 
     cache.rescheduleCleanUpIfIncomplete();
@@ -546,7 +553,7 @@ public void afterWrite_exception() {
     var expected = new RuntimeException();
     var cache = new BoundedLocalCache<Object, Object>(
         Caffeine.newBuilder(), /* cacheLoader= */ null, /* isAsync= */ false) {
-      @Override void maintenance(Runnable task) {
+      @Override void maintenance(@Nullable Runnable task) {
         throw expected;
       }
     };
@@ -662,7 +669,7 @@ public void overflow_update_one(BoundedLocalCache<Int, Int> cache, CacheContext
   @CacheSpec(population = Population.FULL,
       maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE)
   public void overflow_update_many(BoundedLocalCache<Int, Int> cache, CacheContext context) {
-    var updated = Maps.asMap(context.firstMiddleLastKeys(), key -> Int.MAX_VALUE);
+    var updated = Maps.toMap(context.firstMiddleLastKeys(), key -> Int.MAX_VALUE);
     cache.setWeightedSize(BoundedLocalCache.MAXIMUM_CAPACITY);
     cache.evictionLock.lock();
     try {
@@ -689,8 +696,8 @@ public void overflow_update_many(BoundedLocalCache<Int, Int> cache, CacheContext
   @Test(dataProvider = "caches")
   @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.ONE)
   public void evict_alreadyRemoved(BoundedLocalCache<Int, Int> cache, CacheContext context) {
-    var oldEntry = Iterables.get(context.absent().entrySet(), 0);
-    var newEntry = Iterables.get(context.absent().entrySet(), 1);
+    var oldEntry = requireNonNull(Iterables.get(context.absent().entrySet(), 0));
+    var newEntry = requireNonNull(Iterables.get(context.absent().entrySet(), 1));
     var oldValue = cache.put(oldEntry.getKey(), oldEntry.getValue());
     assertThat(oldValue).isNull();
 
@@ -698,7 +705,7 @@ public void evict_alreadyRemoved(BoundedLocalCache<Int, Int> cache, CacheContext
     cache.evictionLock.lock();
     try {
       var lookupKey = cache.nodeFactory.newLookupKey(oldEntry.getKey());
-      var node = cache.data.get(lookupKey);
+      var node = requireNonNull(cache.data.get(lookupKey));
       checkStatus(node, Status.ALIVE);
       ConcurrentTestHarness.execute(() -> {
         var value = cache.put(newEntry.getKey(), newEntry.getValue());
@@ -822,10 +829,11 @@ public void evict_victim_lru(BoundedLocalCache<Int, Int> cache, CacheContext con
     var candidate = cache.evictFromWindow();
     assertThat(candidate).isNotNull();
 
-    var expected = FluentIterable
-        .from(cache.accessOrderProbationDeque())
-        .append(cache.accessOrderProtectedDeque())
-        .transform(Node::getKey).toList();
+    var expected = Stream
+        .concat(cache.accessOrderProbationDeque().stream(),
+            cache.accessOrderProtectedDeque().stream())
+        .map(Node::getKey)
+        .collect(toImmutableList());
     cache.setMaximumSize(0L);
     cache.cleanUp();
 
@@ -903,11 +911,12 @@ public void evict_candidateIsVictim(BoundedLocalCache<Int, Int> cache, CacheCont
     Arrays.fill(cache.frequencySketch().table, 0L);
     cache.setMainProtectedWeightedSize(context.maximumSize() - cache.windowWeightedSize());
 
-    var expected = FluentIterable
-        .from(cache.accessOrderWindowDeque())
-        .append(cache.accessOrderProbationDeque())
-        .append(cache.accessOrderProtectedDeque())
-        .transform(Node::getKey).toList();
+    var expected = Streams
+        .concat(cache.accessOrderWindowDeque().stream(),
+            cache.accessOrderProbationDeque().stream(),
+            cache.accessOrderProtectedDeque().stream())
+        .map(Node::getKey)
+        .collect(toImmutableList());
     cache.setMainProtectedMaximum(0L);
     cache.setWindowMaximum(0L);
     cache.setMaximum(0L);
@@ -929,11 +938,12 @@ public void evict_toZero(BoundedLocalCache<Int, Int> cache, CacheContext context
     }
     Arrays.fill(cache.frequencySketch().table, 0L);
 
-    var expected = FluentIterable
-        .from(cache.accessOrderWindowDeque())
-        .append(cache.accessOrderProbationDeque())
-        .append(cache.accessOrderProtectedDeque())
-        .transform(Node::getKey).toList();
+    var expected = Streams
+        .concat(cache.accessOrderWindowDeque().stream(),
+            cache.accessOrderProbationDeque().stream(),
+            cache.accessOrderProtectedDeque().stream())
+        .map(Node::getKey)
+        .collect(toImmutableList());
     cache.setMaximumSize(0);
     cache.evictEntries();
 
@@ -948,8 +958,8 @@ public void evict_toZero(BoundedLocalCache<Int, Int> cache, CacheContext context
   public void evict_retired_candidate(BoundedLocalCache<Int, Int> cache, CacheContext context) {
     cache.evictionLock.lock();
     try {
-      var expected = cache.accessOrderWindowDeque().peekFirst();
-      var key = expected.getKey();
+      var expected = cache.accessOrderWindowDeque().getFirst();
+      var key = requireNonNull(expected.getKey());
 
       ConcurrentTestHarness.execute(() -> {
         var value = cache.remove(key);
@@ -975,8 +985,8 @@ public void evict_retired_candidate(BoundedLocalCache<Int, Int> cache, CacheCont
   public void evict_retired_victim(BoundedLocalCache<Int, Int> cache, CacheContext context) {
     cache.evictionLock.lock();
     try {
-      var expected = cache.accessOrderProbationDeque().peekFirst();
-      var key = expected.getKey();
+      var expected = cache.accessOrderProbationDeque().getFirst();
+      var key = requireNonNull(expected.getKey());
 
       ConcurrentTestHarness.execute(() -> {
         var value = cache.remove(key);
@@ -998,13 +1008,18 @@ public void evict_retired_victim(BoundedLocalCache<Int, Int> cache, CacheContext
 
   @Test(dataProvider = "caches")
   @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY,
-      maximumSize = Maximum.FULL, weigher = CacheWeigher.VALUE)
+      maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO)
   public void evict_zeroWeight_candidate(BoundedLocalCache<Int, Int> cache, CacheContext context) {
+    when(context.weigher().weigh(any(), any())).thenAnswer(invocation -> {
+      Int value = invocation.getArgument(1);
+      return Math.abs(value.intValue());
+    });
+
     for (int i = 0; i < context.maximumSize(); i++) {
       assertThat(cache.put(Int.valueOf(i), Int.valueOf(1))).isNull();
     }
 
-    var candidate = cache.accessOrderWindowDeque().peekFirst();
+    var candidate = cache.accessOrderWindowDeque().getFirst();
     cache.setWindowWeightedSize(cache.windowWeightedSize() - candidate.getWeight());
     cache.setWeightedSize(cache.weightedSize() - candidate.getWeight());
     candidate.setPolicyWeight(0);
@@ -1019,13 +1034,18 @@ public void evict_zeroWeight_candidate(BoundedLocalCache<Int, Int> cache, CacheC
 
   @Test(dataProvider = "caches")
   @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY,
-      maximumSize = Maximum.FULL, weigher = CacheWeigher.VALUE)
+      maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO)
   public void evict_zeroWeight_victim(BoundedLocalCache<Int, Int> cache, CacheContext context) {
+    when(context.weigher().weigh(any(), any())).thenAnswer(invocation -> {
+      Int value = invocation.getArgument(1);
+      return Math.abs(value.intValue());
+    });
+
     for (int i = 0; i < context.maximumSize(); i++) {
       assertThat(cache.put(Int.valueOf(i), Int.valueOf(1))).isNull();
     }
 
-    var victim = cache.accessOrderProbationDeque().peekFirst();
+    var victim = cache.accessOrderProbationDeque().getFirst();
     cache.setWeightedSize(cache.weightedSize() - victim.getWeight());
     victim.setPolicyWeight(0);
     victim.setWeight(0);
@@ -1134,7 +1154,9 @@ public void evict_update_entryTooBig_window(
     cache.putAll(Map.of(Int.valueOf(9), Int.valueOf(9), Int.valueOf(1), Int.valueOf(1)));
 
     var lookupKey = cache.nodeFactory.newLookupKey(Int.valueOf(1));
-    assertThat(cache.data.get(lookupKey).inWindow()).isTrue();
+    var node = requireNonNull(cache.data.get(lookupKey));
+    assertThat(node.inWindow()).isTrue();
+
     var oldValue = cache.put(Int.valueOf(1), Int.valueOf(20));
     assertThat(oldValue).isEqualTo(1);
 
@@ -1160,7 +1182,9 @@ public void evict_update_entryTooBig_probation(
     }
 
     var lookupKey = cache.nodeFactory.newLookupKey(Int.valueOf(1));
-    assertThat(cache.data.get(lookupKey).inMainProbation()).isTrue();
+    var node = requireNonNull(cache.data.get(lookupKey));
+    assertThat(node.inMainProbation()).isTrue();
+
     var oldValue = cache.put(Int.valueOf(1), Int.valueOf(20));
     assertThat(oldValue).isEqualTo(1);
 
@@ -1190,7 +1214,9 @@ public void evict_update_entryTooBig_protected(
     cache.cleanUp();
 
     var lookupKey = cache.nodeFactory.newLookupKey(Int.valueOf(1));
-    assertThat(cache.data.get(lookupKey).inMainProtected()).isTrue();
+    var node = requireNonNull(cache.data.get(lookupKey));
+    assertThat(node.inMainProtected()).isTrue();
+
     var oldValue = cache.put(Int.valueOf(1), Int.valueOf(20));
     assertThat(oldValue).isEqualTo(1);
 
@@ -1216,6 +1242,8 @@ public void evict_resurrect_collected(BoundedLocalCache<Int, Int> cache, CacheCo
     assertThat(initialValue).isNull();
 
     var node = cache.data.get(cache.referenceKey(key));
+    assertThat(node).isNotNull();
+
     @SuppressWarnings("unchecked")
     var ref = (Reference<Int>) node.getValueReference();
     ref.enqueue();
@@ -1233,7 +1261,10 @@ public void evict_resurrect_collected(BoundedLocalCache<Int, Int> cache, CacheCo
       });
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(evictor.get().getState()));
+      await().until(() -> {
+        var thread = evictor.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
 
       return newValue;
     });
@@ -1268,7 +1299,10 @@ public void evict_resurrect_weight(Cache<Int, List<Int>> cache, CacheContext con
 
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(evictor.get().getState()));
+      await().until(() -> {
+        var thread = evictor.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
 
       return List.of();
     });
@@ -1302,7 +1336,10 @@ public void evict_resurrect_expireAfter(Cache<Int, Int> cache, CacheContext cont
 
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(evictor.get().getState()));
+      await().until(() -> {
+        var thread = evictor.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
       return key.negate();
     });
     await().untilTrue(done);
@@ -1333,7 +1370,10 @@ public void evict_resurrect_expireAfterAccess(Cache<Int, Int> cache, CacheContex
 
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(evictor.get().getState()));
+      await().until(() -> {
+        var thread = evictor.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
       cache.policy().expireAfterAccess().orElseThrow().setExpiresAfter(Duration.ofHours(1));
       return v;
     });
@@ -1364,7 +1404,10 @@ public void evict_resurrect_expireAfterWrite(Cache<Int, Int> cache, CacheContext
 
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(evictor.get().getState()));
+      await().until(() -> {
+        var thread = evictor.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
       cache.policy().expireAfterWrite().orElseThrow().setExpiresAfter(Duration.ofHours(1));
       return v;
     });
@@ -1395,7 +1438,10 @@ public void evict_resurrect_expireAfterWrite_entry(Cache<Int, Int> cache, CacheC
 
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(evictor.get().getState()));
+      await().until(() -> {
+        var thread = evictor.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
       return key.negate();
     });
     await().untilTrue(done);
@@ -1417,7 +1463,7 @@ public void evict_resurrect_expireAfterVar(
     var started = new AtomicBoolean();
     var done = new AtomicBoolean();
     var evictor = new AtomicReference<Thread>();
-    var node = cache.data.get(cache.referenceKey(key));
+    var node = requireNonNull(cache.data.get(cache.referenceKey(key)));
     synchronized (node) {
       context.ticker().advance(Duration.ofHours(1));
       ConcurrentTestHarness.execute(() -> {
@@ -1429,7 +1475,10 @@ public void evict_resurrect_expireAfterVar(
 
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(evictor.get().getState()));
+      await().until(() -> {
+        var thread = evictor.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
       node.setVariableTime(context.ticker().read() + TimeUnit.DAYS.toNanos(1));
     }
     await().untilTrue(done);
@@ -1500,7 +1549,8 @@ public void evictEntry_absent(BoundedLocalCache<Int, Int> cache, CacheContext co
   public void updateRecency_onGet(BoundedLocalCache<Int, Int> cache, CacheContext context) {
     var first = firstBeforeAccess(cache, context);
     updateRecency(cache, context, () -> {
-      var value = cache.get(first.getKey());
+      var key = requireNonNull(first.getKey());
+      var value = cache.get(key);
       assertThat(value).isNotNull();
     });
   }
@@ -1510,7 +1560,8 @@ public void updateRecency_onGet(BoundedLocalCache<Int, Int> cache, CacheContext
   public void updateRecency_onPutIfAbsent(BoundedLocalCache<Int, Int> cache, CacheContext context) {
     var first = firstBeforeAccess(cache, context);
     updateRecency(cache, context, () -> {
-      var oldValue = cache.putIfAbsent(first.getKey(), first.getKey());
+      var key = requireNonNull(first.getKey());
+      var oldValue = cache.putIfAbsent(key, key);
       assertThat(oldValue).isNotNull();
     });
   }
@@ -1520,7 +1571,8 @@ public void updateRecency_onPutIfAbsent(BoundedLocalCache<Int, Int> cache, Cache
   public void updateRecency_onPut(BoundedLocalCache<Int, Int> cache, CacheContext context) {
     var first = firstBeforeAccess(cache, context);
     updateRecency(cache, context, () -> {
-      var oldValue = cache.put(first.getKey(), first.getKey());
+      var key = requireNonNull(first.getKey());
+      var oldValue = cache.put(key, key);
       assertThat(oldValue).isNotNull();
     });
   }
@@ -1530,7 +1582,8 @@ public void updateRecency_onPut(BoundedLocalCache<Int, Int> cache, CacheContext
   public void updateRecency_onReplace(BoundedLocalCache<Int, Int> cache, CacheContext context) {
     var first = firstBeforeAccess(cache, context);
     updateRecency(cache, context, () -> {
-      var oldValue = cache.replace(first.getKey(), first.getKey());
+      var key = requireNonNull(first.getKey());
+      var oldValue = cache.replace(key, key);
       assertThat(oldValue).isNotNull();
     });
   }
@@ -1540,10 +1593,11 @@ public void updateRecency_onReplace(BoundedLocalCache<Int, Int> cache, CacheCont
   public void updateRecency_onReplaceConditionally(
       BoundedLocalCache<Int, Int> cache, CacheContext context) {
     var first = firstBeforeAccess(cache, context);
-    Int value = first.getValue();
+    Int value = requireNonNull(first.getValue());
 
     updateRecency(cache, context, () -> {
-      boolean replaced = cache.replace(first.getKey(), value, value);
+      var key = requireNonNull(first.getKey());
+      boolean replaced = cache.replace(key, value, value);
       assertThat(replaced).isTrue();
     });
   }
@@ -1551,8 +1605,8 @@ public void updateRecency_onReplaceConditionally(
   private static Node<Int, Int> firstBeforeAccess(
       BoundedLocalCache<Int, Int> cache, CacheContext context) {
     return context.isZeroWeighted()
-        ? cache.accessOrderWindowDeque().peek()
-        : cache.accessOrderProbationDeque().peek();
+        ? cache.accessOrderWindowDeque().getFirst()
+        : cache.accessOrderProbationDeque().getFirst();
   }
 
   private static void updateRecency(BoundedLocalCache<Int, Int> cache,
@@ -1575,6 +1629,7 @@ private static void updateRecency(BoundedLocalCache<Int, Int> cache,
   @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL)
   public void exceedsMaximumBufferSize_onRead(
       BoundedLocalCache<Int, Int> cache, CacheContext context) {
+    @SuppressWarnings("NullAway")
     var dummy = cache.nodeFactory.newNode(
         new WeakKeyReference<>(null, null), null, null, 1, 0);
     cache.frequencySketch().ensureCapacity(1);
@@ -1916,20 +1971,23 @@ public void put_warnIfEvictionBlocked(BoundedLocalCache<Int, Int> cache, CacheCo
 
       var halfWaitTime = Duration.ofNanos(WARN_AFTER_LOCK_WAIT_NANOS / 2);
       await().until(cache.evictionLock::hasQueuedThreads);
+      assertThat(thread.get()).isNotNull();
       thread.get().interrupt();
 
       Uninterruptibles.sleepUninterruptibly(halfWaitTime);
       assertThat(cache.evictionLock.hasQueuedThreads()).isTrue();
-      assertThat(testLogger.get().getAllLoggingEvents()).isEmpty();
+      var threadLogger = requireNonNull(testLogger.get());
+      assertThat(threadLogger.getAllLoggingEvents()).isEmpty();
 
       Uninterruptibles.sleepUninterruptibly(halfWaitTime.plusMillis(500));
-      await().until(() -> !testLogger.get().getAllLoggingEvents().isEmpty());
+      await().until(() -> !threadLogger.getAllLoggingEvents().isEmpty());
 
       assertThat(cache.evictionLock.hasQueuedThreads()).isTrue();
 
       var event = Iterables.getOnlyElement(TestLoggerFactory.getAllLoggingEvents().stream()
           .filter(e -> e.getLevel() == WARN)
           .collect(toImmutableList()));
+      requireNonNull(event);
       assertThat(event.getFormattedMessage()).contains("excessive wait times");
       assertThat(event.getThrowable().orElseThrow()).isInstanceOf(TimeoutException.class);
     } finally {
@@ -1945,7 +2003,7 @@ public void put_spinToCompute(BoundedLocalCache<Int, Int> cache, CacheContext co
     var initialValue = cache.put(context.absentKey(), context.absentValue());
     assertThat(initialValue).isNull();
 
-    var node = cache.data.get(context.absentKey());
+    var node = requireNonNull(cache.data.get(context.absentKey()));
     node.retire();
 
     var future = new Future<?>[1];
@@ -1959,7 +2017,10 @@ public void put_spinToCompute(BoundedLocalCache<Int, Int> cache, CacheContext co
 
       var threadState = EnumSet.of(BLOCKED, WAITING);
       await().untilAtomic(writer, is(not(nullValue())));
-      await().until(() -> threadState.contains(writer.get().getState()));
+      await().until(() -> {
+        var thread = writer.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
 
       return null;
     });
@@ -1979,19 +2040,23 @@ public void putIfAbsent_expireAfterRead(BoundedLocalCache<Int, Int> cache, Cache
     context.ticker().advance(Duration.ofHours(1));
     var result = new AtomicReference<Int>();
     long currentDuration = 1;
+    requireNonNull(node);
 
     synchronized (node) {
       var started = new AtomicBoolean();
-      var thread = new AtomicReference<Thread>();
+      var writer = new AtomicReference<Thread>();
       ConcurrentTestHarness.execute(() -> {
-        thread.set(Thread.currentThread());
+        writer.set(Thread.currentThread());
         started.set(true);
         var value = cache.putIfAbsent(context.firstKey(), context.absentValue());
         result.set(value);
       });
       await().untilTrue(started);
       var threadState = EnumSet.of(BLOCKED, WAITING);
-      await().until(() -> threadState.contains(thread.get().getState()));
+      await().until(() -> {
+        var thread = writer.get();
+        return (thread != null) && threadState.contains(thread.getState());
+      });
       node.setVariableTime(context.ticker().read() + currentDuration);
     }
 
@@ -2019,15 +2084,16 @@ public void unschedule_cleanUp(BoundedLocalCache<Int, Int> cache, CacheContext c
       var value = cache.put(Int.valueOf(i), Int.valueOf(-i));
       assertThat(value).isNull();
     }
-    assertThat(cache.pacer().nextFireTime).isNotEqualTo(0);
-    assertThat(cache.pacer().future).isNotNull();
+    var pacer = requireNonNull(cache.pacer());
+    assertThat(pacer.nextFireTime).isNotEqualTo(0);
+    assertThat(pacer.future).isNotNull();
 
     context.ticker().advance(Duration.ofHours(1));
     cache.cleanUp();
 
     verify(future).cancel(false);
-    assertThat(cache.pacer().nextFireTime).isEqualTo(0);
-    assertThat(cache.pacer().future).isNull();
+    assertThat(pacer.nextFireTime).isEqualTo(0);
+    assertThat(pacer.future).isNull();
   }
 
   @Test(dataProvider = "caches")
@@ -2045,13 +2111,14 @@ public void unschedule_invalidateAll(BoundedLocalCache<Int, Int> cache, CacheCon
       var value = cache.put(Int.valueOf(i), Int.valueOf(-i));
       assertThat(value).isNull();
     }
-    assertThat(cache.pacer().nextFireTime).isNotEqualTo(0);
-    assertThat(cache.pacer().future).isNotNull();
+    var pacer = requireNonNull(cache.pacer());
+    assertThat(pacer.nextFireTime).isNotEqualTo(0);
+    assertThat(pacer.future).isNotNull();
 
     cache.clear();
     verify(future).cancel(false);
-    assertThat(cache.pacer().nextFireTime).isEqualTo(0);
-    assertThat(cache.pacer().future).isNull();
+    assertThat(pacer.nextFireTime).isEqualTo(0);
+    assertThat(pacer.future).isNull();
   }
 
   @Test(dataProvider = "caches")
@@ -2076,8 +2143,9 @@ public void expirationDelay_window(BoundedLocalCache<Int, Int> cache, CacheConte
         node.setAccessTime(context.ticker().read());
       }
       for (var node : FluentIterable.from(cache.accessOrderProbationDeque()).skip(5).toList()) {
-        var value = cache.get(node.getKey());
-        assertThat(value).isEqualTo(node.getKey());
+        var key = requireNonNull(node.getKey());
+        var value = cache.get(key);
+        assertThat(value).isEqualTo(key);
       }
       context.ticker().advance(Duration.ofNanos(stepSize));
       cache.cleanUp();
@@ -2108,8 +2176,9 @@ public void expirationDelay_probation(BoundedLocalCache<Int, Int> cache, CacheCo
       node.setAccessTime(context.ticker().read());
     }
     for (var node : FluentIterable.from(cache.accessOrderProbationDeque()).skip(5).toList()) {
-      var value = cache.get(node.getKey());
-      assertThat(value).isEqualTo(node.getKey());
+      var key = requireNonNull(node.getKey());
+      var value = cache.get(key);
+      assertThat(value).isEqualTo(key);
     }
     context.ticker().advance(Duration.ofNanos(stepSize));
     cache.cleanUp();
@@ -2133,8 +2202,9 @@ public void expirationDelay_protected(BoundedLocalCache<Int, Int> cache, CacheCo
     }
 
     for (var node : FluentIterable.from(cache.accessOrderProbationDeque()).skip(5).toList()) {
-      var value = cache.get(node.getKey());
-      assertThat(value).isEqualTo(node.getKey());
+      var key = requireNonNull(node.getKey());
+      var value = cache.get(key);
+      assertThat(value).isEqualTo(key);
     }
     context.ticker().advance(Duration.ofNanos(stepSize));
     cache.cleanUp();
@@ -2227,7 +2297,7 @@ public void refreshIfNeeded_liveliness(CacheContext context) {
     assertThat(oldValue).isNull();
 
     // Remove the entry after the read, but before the refresh, and leave it as retired
-    var node = cache.data.get(context.absentKey());
+    var node = requireNonNull(cache.data.get(context.absentKey()));
     doAnswer(invocation -> {
       ConcurrentTestHarness.execute(() -> {
         var value = cache.remove(context.absentKey());
@@ -2276,7 +2346,7 @@ public CompletableFuture<Int> asyncReload(Int key, Int oldValue, Executor execut
     var localCache = asBoundedLocalCache(cache);
     cache.put(context.absentKey(), context.absentValue());
     var lookupKey = localCache.nodeFactory.newLookupKey(context.absentKey());
-    var node = localCache.data.get(lookupKey);
+    var node = requireNonNull(localCache.data.get(lookupKey));
     var refreshes = localCache.refreshes();
 
     context.ticker().advance(Duration.ofMinutes(2));
@@ -2379,7 +2449,7 @@ public void brokenEquality_eviction(BoundedLocalCache<Object, Int> cache,
     assertThat(context).notifications().isEmpty();
     assertThat(cache.estimatedSize()).isEqualTo(1);
 
-    var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents());
+    var event = requireNonNull(Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()));
     assertThat(event.getThrowable().orElseThrow()).isInstanceOf(IllegalStateException.class);
     checkBrokenEqualityMessage(cache, key, event.getFormattedMessage());
     assertThat(event.getLevel()).isEqualTo(ERROR);
@@ -2408,7 +2478,7 @@ public void brokenEquality_expiration(
     assertThat(context).notifications().isEmpty();
     assertThat(cache.estimatedSize()).isEqualTo(1);
 
-    var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents());
+    var event = requireNonNull(Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()));
     assertThat(event.getThrowable().orElseThrow()).isInstanceOf(IllegalStateException.class);
     checkBrokenEqualityMessage(cache, key, event.getFormattedMessage());
     assertThat(event.getLevel()).isEqualTo(ERROR);
@@ -2430,7 +2500,7 @@ public void brokenEquality_clear(BoundedLocalCache<Object, Int> cache, CacheCont
     assertThat(context).notifications().isEmpty();
     assertThat(cache.estimatedSize()).isEqualTo(1);
 
-    var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents());
+    var event = requireNonNull(Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()));
     assertThat(event.getThrowable().orElseThrow()).isInstanceOf(IllegalStateException.class);
     checkBrokenEqualityMessage(cache, key, event.getFormattedMessage());
     assertThat(event.getLevel()).isEqualTo(ERROR);
@@ -2515,12 +2585,11 @@ private static void testForBrokenEquality(BoundedLocalCache<MutableInt, Int> cac
 
     var e = assertThrows(IllegalStateException.class, () -> task.accept(key));
     checkBrokenEqualityMessage(cache, key, e.getMessage());
-
     cache.data.clear();
   }
 
   private static void checkBrokenEqualityMessage(
-      BoundedLocalCache<?, ?> cache, Object key, String msg) {
+      BoundedLocalCache<?, ?> cache, Object key, @Nullable String msg) {
     assertThat(msg).contains("An invalid state was detected");
     assertThat(msg).contains("cache type: " + cache.getClass().getSimpleName());
     assertThat(msg).contains("node type: " + cache.nodeFactory.getClass().getSimpleName());
@@ -2539,9 +2608,9 @@ public void reflectivelyConstruct() throws ReflectiveOperationException {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void cacheFactory_null() {
-    assertThrows(NullPointerException.class,
-        () -> LocalCacheFactory.loadFactory(null));
+    assertThrows(NullPointerException.class, () -> LocalCacheFactory.loadFactory(null));
   }
 
   @Test
@@ -2638,6 +2707,7 @@ public void cacheFactory_byMethodHandle(
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void nodeFactory_null() {
     assertThrows(NullPointerException.class, () -> NodeFactory.loadFactory(/* className= */ null));
   }
@@ -2712,6 +2782,7 @@ public void cache_unsupported() {
 
   @Test
   public void cleanupTask_ignore() {
+    @SuppressWarnings("NullAway")
     var task = new PerformCleanupTask(null);
     assertThat(task.getRawResult()).isNull();
     assertThat(task.cancel(false)).isFalse();
@@ -2784,7 +2855,7 @@ public void policy_unsupported() {
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.SINGLETON, expiryTime = Expire.ONE_MINUTE)
   public void expireAfterRead_disabled(BoundedLocalCache<Int, Int> cache, CacheContext context) {
-    var node = Iterables.getOnlyElement(cache.data.values());
+    var node = requireNonNull(Iterables.getOnlyElement(cache.data.values()));
     long duration = cache.expireAfterRead(node, node.getKey(), node.getValue(),
         cache.expiry(), context.ticker().read());
     var expiresAt = cache.expiresVariable()
@@ -2837,17 +2908,18 @@ static final class BadBoundedLocalCache<K, V> extends BoundedLocalCache<K, V> {
 
     @SuppressWarnings("unchecked")
     BadBoundedLocalCache(Caffeine<K, V> builder,
-        AsyncCacheLoader<? super K, V> cacheLoader, boolean async) {
+        @Nullable AsyncCacheLoader<? super K, V> cacheLoader, boolean async) {
       super(builder, (AsyncCacheLoader<K, V>) cacheLoader, async);
       throw new IllegalStateException();
     }
   }
   static final class BadLocalCacheFactory implements LocalCacheFactory {
     @Override public <K, V> BoundedLocalCache<K, V> newInstance(Caffeine<K, V> builder,
-        AsyncCacheLoader<? super K, V> cacheLoader, boolean async) {
+        @Nullable AsyncCacheLoader<? super K, V> cacheLoader, boolean async) {
       throw new IllegalStateException();
     }
   }
+  @NullUnmarked
   static final class BadNode extends Node<Object, Object> {
     public BadNode() {
       throw new IllegalStateException();
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java
index feb55527b9..af7ea6bd3c 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java
@@ -25,6 +25,7 @@
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -121,6 +122,7 @@ public void estimatedSize(Cache<Int, Int> cache, CacheContext context) {
   /* --------------- getIfPresent --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getIfPresent_nullKey(Cache<Int, Int> cache, CacheContext context) {
@@ -148,6 +150,7 @@ public void getIfPresent_present(Cache<Int, Int> cache, CacheContext context) {
 
   @CacheSpec
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void get_nullKey(Cache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.get(null, identity()));
@@ -155,6 +158,7 @@ public void get_nullKey(Cache<Int, Int> cache, CacheContext context) {
 
   @CacheSpec
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void get_nullLoader(Cache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.get(context.absentKey(), null));
@@ -162,12 +166,14 @@ public void get_nullLoader(Cache<Int, Int> cache, CacheContext context) {
 
   @CacheSpec
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void get_nullKeyAndLoader(Cache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.get(null, null));
   }
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void get_absent_null(Cache<Int, Int> cache, CacheContext context) {
     assertThat(cache.get(context.absentKey(), k -> null)).isNull();
@@ -228,6 +234,7 @@ public void get_present(Cache<Int, Int> cache, CacheContext context) {
   /* --------------- getAllPresent --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAllPresent_iterable_null(Cache<Int, Int> cache, CacheContext context) {
@@ -345,7 +352,7 @@ final class Key {
       keys.add(new Key());
     }
 
-    Key key = Iterables.getLast(keys);
+    Key key = requireNonNull(Iterables.getLast(keys));
     Int value = context.absentValue();
     cache.put(key, value);
 
@@ -355,6 +362,7 @@ final class Key {
 
   /* --------------- getAll --------------- */
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAll_iterable_null(Cache<Int, Int> cache, CacheContext context) {
@@ -378,12 +386,14 @@ public void getAll_iterable_empty(Cache<Int, Int> cache, CacheContext context) {
     assertThat(context).stats().hits(0).misses(0);
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAll_function_null(Cache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.getAll(context.absentKeys(), null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAll_function_nullValue(Cache<Int, Int> cache, CacheContext context) {
@@ -589,13 +599,14 @@ final class Key {
         return 0; // to put keys in one bucket
       }
     }
+    @SuppressWarnings("NullAway")
     Cache<Object, Int> cache = context.build(key -> null);
 
     var keys = new ArrayList<Key>();
     for (int i = 0; i < Population.FULL.size(); i++) {
       keys.add(intern(new Key()));
     }
-    Key key = Iterables.getLast(keys);
+    Key key = requireNonNull(Iterables.getLast(keys));
     Int value = context.absentValue();
     cache.put(key, value);
 
@@ -643,7 +654,7 @@ public void put_replace_sameValue(Cache<Int, Int> cache, CacheContext context) {
   public void put_replace_sameInstance(Cache<Int, Int> cache, CacheContext context) {
     var replaced = new HashMap<Int, Int>();
     for (Int key : context.firstMiddleLastKeys()) {
-      Int value = context.original().get(key);
+      Int value = requireNonNull(context.original().get(key));
       cache.put(key, value);
       assertThat(cache).containsEntry(key, value);
       replaced.put(key, value);
@@ -673,6 +684,7 @@ public void put_replace_differentValue(Cache<Int, Int> cache, CacheContext conte
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void put_nullKey(Cache<Int, Int> cache, CacheContext context) {
@@ -680,6 +692,7 @@ public void put_nullKey(Cache<Int, Int> cache, CacheContext context) {
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void put_nullValue(Cache<Int, Int> cache, CacheContext context) {
@@ -687,6 +700,7 @@ public void put_nullValue(Cache<Int, Int> cache, CacheContext context) {
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void put_nullKeyAndValue(Cache<Int, Int> cache, CacheContext context) {
@@ -750,6 +764,7 @@ public void putAll_empty(Cache<Int, Int> cache, CacheContext context) {
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void putAll_null(Cache<Int, Int> cache, CacheContext context) {
@@ -781,6 +796,7 @@ public void invalidate_present(Cache<Int, Int> cache, CacheContext context) {
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void invalidate_nullKey(Cache<Int, Int> cache, CacheContext context) {
@@ -828,6 +844,7 @@ public void invalidateAll_full(Cache<Int, Int> cache, CacheContext context) {
 
   @CacheSpec
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   public void invalidateAll_null(Cache<Int, Int> cache, CacheContext context) {
     assertThrows(NullPointerException.class, () -> cache.invalidateAll(null));
@@ -860,7 +877,7 @@ public void invalidateAll_removalListener_writeback(Cache<Int, Int> cache, Cache
     cache.invalidateAll();
     assertThat(context).removalNotifications().withCause(EXPLICIT)
         .contains(context.original()).exclusively();
-    assertThat(cache).containsExactlyEntriesIn(Maps.asMap(context.original().keySet(), key -> key));
+    assertThat(cache).containsExactlyEntriesIn(Maps.toMap(context.original().keySet(), key -> key));
   }
 
   /* --------------- cleanup --------------- */
@@ -902,6 +919,7 @@ public void removalListener_submit_error_log(Cache<Int, Int> cache, CacheContext
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.EMPTY, refreshAfterWrite = Expire.DISABLED,
       expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED,
@@ -1040,6 +1058,7 @@ public void stats(Cache<Int, Int> cache, CacheContext context) {
   /* --------------- Policy: getIfPresentQuietly --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getIfPresentQuietly_nullKey(Cache<Int, Int> cache, CacheContext context) {
@@ -1066,6 +1085,7 @@ public void getIfPresentQuietly_present(Cache<Int, Int> cache, CacheContext cont
   /* --------------- Policy: getEntryIfPresentQuietly --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getEntryIfPresentQuietly_nullKey(Cache<Int, Int> cache, CacheContext context) {
@@ -1085,7 +1105,7 @@ public void getEntryIfPresentQuietly_absent(Cache<Int, Int> cache, CacheContext
       removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getEntryIfPresentQuietly_present(Cache<Int, Int> cache, CacheContext context) {
     for (Int key : context.firstMiddleLastKeys()) {
-      var entry = cache.policy().getEntryIfPresentQuietly(key);
+      var entry = requireNonNull(cache.policy().getEntryIfPresentQuietly(key));
       assertThat(context).containsEntry(entry);
     }
   }
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java
index aa2b2fc4dd..4b01a97910 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java
@@ -354,21 +354,25 @@ public void testEqualsAndHashCode() {
         .testEquals();
   }
 
+  @SuppressWarnings("NullAway")
   public void testMaximumWeight_withWeigher() {
     Caffeine<Object, Object> builder = Caffeine.from(parse("maximumWeight=9000"));
     assertThat(builder.weigher((k, v) -> 42).build(k -> null)).isNotNull();
   }
 
+  @SuppressWarnings("NullAway")
   public void testMaximumWeight_withoutWeigher() {
     Caffeine<Object, Object> builder = Caffeine.from(parse("maximumWeight=9000"));
     assertThrows(IllegalStateException.class, () -> builder.build(k -> null));
   }
 
+  @SuppressWarnings("NullAway")
   public void testMaximumSize_withWeigher() {
     Caffeine<Object, Object> builder = Caffeine.from(parse("maximumSize=9000"));
     assertThat(builder.weigher((k, v) -> 42).build(k -> null)).isNotNull();
   }
 
+  @SuppressWarnings("NullAway")
   public void testMaximumSize_withoutWeigher() {
     Caffeine<Object, Object> builder = Caffeine.from(parse("maximumSize=9000"));
     assertThat(builder.build(k -> null)).isNotNull();
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java
index 7bd8d5d09f..7b5edb7131 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java
@@ -28,11 +28,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
 
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.Mockito;
 import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.github.benmanes.caffeine.cache.stats.StatsCounter;
@@ -56,16 +53,12 @@
  * @author ben.manes@gmail.com (Ben Manes)
  */
 public final class CaffeineTest {
-  @Mock StatsCounter statsCounter;
-  @Mock Expiry<Object, Object> expiry;
-  @Mock CacheLoader<Object, Object> loader;
+  private static final Expiry<Object, Object> expiry = Expiry.accessing(
+      (key, value) -> { throw new AssertionError(); });
+  private static final CacheLoader<Object, Object> loader =
+      key -> { throw new AssertionError(); };
 
-  @BeforeClass
-  public void beforeClass() throws Exception {
-    MockitoAnnotations.openMocks(this).close();
-  }
-
-  @BeforeMethod @AfterMethod
+  @AfterMethod
   public void reset() {
     TestLoggerFactory.clear();
   }
@@ -104,6 +97,7 @@ public void configured() {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void fromSpec_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.from((CaffeineSpec) null));
   }
@@ -126,6 +120,7 @@ public void fromSpec() {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void fromString_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.from((String) null));
   }
@@ -196,6 +191,7 @@ public void calculateHashMapCapacity() {
   /* --------------- loading --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void loading_nullLoader() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().build(null));
   }
@@ -224,6 +220,7 @@ public void async_weakKeys_evictionListener() {
   /* --------------- async loader --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void asyncLoader_nullLoader() {
     assertThrows(NullPointerException.class, () ->
         Caffeine.newBuilder().buildAsync((CacheLoader<Object, Object>) null));
@@ -377,6 +374,7 @@ public void maximumWeight_large() {
   /* --------------- weigher --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void weigher_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().weigher(null));
   }
@@ -589,6 +587,7 @@ public void expireAfterWrite_duration_excessive() {
   /* --------------- expiry --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void expireAfter_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().expireAfter(null));
   }
@@ -744,6 +743,7 @@ public void softValues() {
   /* --------------- scheduler --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void scheduler_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().scheduler(null));
   }
@@ -764,7 +764,7 @@ public void scheduler_system() {
 
   @Test
   public void scheduler_custom() {
-    Scheduler scheduler = (executor, task, delay, unit) -> DisabledFuture.INSTANCE;
+    Scheduler scheduler = (executor, task, delay, unit) -> DisabledFuture.instance();
     var builder = Caffeine.newBuilder().scheduler(scheduler);
     assertThat(((GuardedScheduler) builder.getScheduler()).delegate).isSameInstanceAs(scheduler);
     assertThat(builder.build()).isNotNull();
@@ -773,6 +773,7 @@ public void scheduler_custom() {
   /* --------------- executor --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void executor_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().executor(null));
   }
@@ -793,6 +794,7 @@ public void executor() {
   /* --------------- ticker --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void ticker_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().ticker(null));
   }
@@ -815,6 +817,7 @@ public void ticker() {
   /* --------------- stats --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void recordStats_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().recordStats(null));
   }
@@ -822,7 +825,7 @@ public void recordStats_null() {
   @Test
   public void recordStats_twice() {
     var type = IllegalStateException.class;
-    Supplier<StatsCounter> supplier = () -> statsCounter;
+    Supplier<StatsCounter> supplier = Mockito::mock;
     assertThrows(type, () -> Caffeine.newBuilder().recordStats().recordStats());
     assertThrows(type, () -> Caffeine.newBuilder().recordStats(supplier).recordStats());
     assertThrows(type, () -> Caffeine.newBuilder().recordStats().recordStats(supplier));
@@ -838,9 +841,19 @@ public void recordStats() {
 
   @Test
   public void recordStats_custom() {
-    Supplier<StatsCounter> supplier = () -> statsCounter;
-    var builder = Caffeine.newBuilder().recordStats(supplier);
-    builder.statsCounterSupplier.get().recordEviction(1, RemovalCause.SIZE);
+    StatsCounter statsCounter = Mockito.mock();
+    var builder = Caffeine.newBuilder().recordStats(() -> statsCounter);
+    assertThat(builder.statsCounterSupplier).isNotNull();
+
+    var counter1 = builder.statsCounterSupplier.get();
+    var counter2 = builder.statsCounterSupplier.get();
+    assertThat(counter1).isNotSameInstanceAs(counter2);
+    assertThat(counter1).isNotNull();
+    assertThat(counter2).isNotNull();
+
+    assertThat(counter1.getClass().getName())
+        .isEqualTo("com.github.benmanes.caffeine.cache.stats.GuardedStatsCounter");
+    counter1.recordEviction(1, RemovalCause.SIZE);
     verify(statsCounter).recordEviction(1, RemovalCause.SIZE);
     assertThat(builder.build()).isNotNull();
   }
@@ -848,6 +861,7 @@ public void recordStats_custom() {
   /* --------------- removalListener --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void removalListener_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().removalListener(null));
   }
@@ -869,6 +883,7 @@ public void removalListener() {
   /* --------------- evictionListener --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void evictionListener_null() {
     assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().evictionListener(null));
   }
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java
index ca451325d4..e72fe319fe 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java
@@ -1199,12 +1199,14 @@ public void coldest_snapshot(Cache<Int, Int> cache,
     assertThat(coldest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL)
   public void coldestFunc_null(CacheContext context, Eviction<Int, Int> eviction) {
     assertThrows(NullPointerException.class, () -> eviction.coldest(null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL)
   public void coldestFunc_nullResult(CacheContext context, Eviction<Int, Int> eviction) {
@@ -1404,12 +1406,14 @@ public void hottest_snapshot(Cache<Int, Int> cache,
     assertThat(hottest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL)
   public void hottestFunc_null(CacheContext context, Eviction<Int, Int> eviction) {
     assertThrows(NullPointerException.class, () -> eviction.hottest(null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL)
   public void hottestFunc_nullResult(CacheContext context, Eviction<Int, Int> eviction) {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java
index 4342d0f82a..f66aa1c8a7 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java
@@ -142,7 +142,7 @@ public void schedule(Cache<Int, Int> cache, CacheContext context) {
     var delay = ArgumentCaptor.forClass(long.class);
     var task = ArgumentCaptor.forClass(Runnable.class);
     when(context.scheduler().schedule(eq(context.executor()), task.capture(), delay.capture(),
-        eq(TimeUnit.NANOSECONDS))).then(invocation -> DisabledFuture.INSTANCE);
+        eq(TimeUnit.NANOSECONDS))).then(invocation -> DisabledFuture.instance());
 
     cache.put(context.absentKey(), context.absentValue());
 
@@ -556,10 +556,10 @@ public void getAll(AsyncCache<Int, Int> cache, CacheContext context) {
     var keys = context.firstMiddleLastKeys();
     context.ticker().advance(Duration.ofMinutes(1));
     cache.getAll(context.firstMiddleLastKeys(),
-        keysToLoad -> Maps.asMap(keysToLoad, identity())).join();
+        keysToLoad -> Maps.toMap(keysToLoad, identity())).join();
 
-    var expected = Maps.asMap(keys, identity());
-    assertThat(cache.getAll(keys, keysToLoad -> Maps.asMap(keysToLoad, identity())).join())
+    var expected = Maps.toMap(keys, identity());
+    assertThat(cache.getAll(keys, keysToLoad -> Maps.toMap(keysToLoad, identity())).join())
         .containsExactlyEntriesIn(expected).inOrder();
     assertThat(context).notifications().withCause(EXPIRED)
         .contains(context.original()).exclusively();
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java
index 86d6486490..85dffcc2fd 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java
@@ -21,11 +21,13 @@
 import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.AFTER_ACCESS;
 import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.VARIABLE;
 import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat;
+import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat;
 import static com.github.benmanes.caffeine.testing.MapSubject.assertThat;
 import static com.google.common.base.Functions.identity;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static org.junit.Assert.assertThrows;
 import static org.slf4j.event.Level.WARN;
 
@@ -97,7 +99,7 @@ public void getIfPresent(Cache<Int, Int> cache, CacheContext context) {
       expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE,
       expireAfterAccess = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL })
   public void get(Cache<Int, Int> cache, CacheContext context) {
-    Function<Int, Int> mappingFunction = context.original()::get;
+    Function<Int, Int> mappingFunction = key -> requireNonNull(context.original().get(key));
     context.ticker().advance(Duration.ofSeconds(30));
     var value1 = cache.get(context.firstKey(), mappingFunction);
     assertThat(value1).isEqualTo(context.original().get(context.firstKey()));
@@ -146,21 +148,21 @@ public void getAllPresent(Cache<Int, Int> cache, CacheContext context) {
   public void getAll(Cache<Int, Int> cache, CacheContext context) {
     context.ticker().advance(Duration.ofSeconds(30));
     var result1 = cache.getAll(List.of(context.firstKey(), context.middleKey()),
-        keys -> Maps.asMap(keys, identity()));
+        keys -> Maps.toMap(keys, identity()));
     assertThat(result1).containsExactly(context.firstKey(), context.firstKey().negate(),
         context.middleKey(), context.middleKey().negate());
 
     context.ticker().advance(Duration.ofSeconds(45));
     cache.cleanUp();
     var result2 = cache.getAll(List.of(context.firstKey(), context.absentKey()),
-        keys -> Maps.asMap(keys, identity()));
+        keys -> Maps.toMap(keys, identity()));
     assertThat(result2).containsExactly(context.firstKey(), context.firstKey().negate(),
         context.absentKey(), context.absentKey());
 
     context.ticker().advance(Duration.ofSeconds(45));
     cache.cleanUp();
     var result3 = cache.getAll(List.of(context.middleKey(), context.absentKey()),
-        keys -> Maps.asMap(keys, identity()));
+        keys -> Maps.toMap(keys, identity()));
     assertThat(result3).containsExactly(context.middleKey(), context.middleKey(),
         context.absentKey(), context.absentKey());
     assertThat(cache).hasSize(3);
@@ -233,7 +235,9 @@ public void getAll_loading(LoadingCache<Int, Int> cache, CacheContext context) {
       expireAfterAccess = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL })
   public void getIfPresent_async(AsyncCache<Int, Int> cache, CacheContext context) {
     context.ticker().advance(Duration.ofSeconds(30));
-    cache.getIfPresent(context.firstKey()).join();
+    assertThat(cache.getIfPresent(context.firstKey()))
+        .succeedsWith(context.original().get(context.firstKey()));
+
     context.ticker().advance(Duration.ofSeconds(45));
     assertThat(cache).containsKey(context.firstKey());
     assertThat(cache).doesNotContainKey(context.lastKey());
@@ -433,6 +437,7 @@ public void oldest_snapshot(Cache<Int, Int> cache, CacheContext context,
     assertThat(oldest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE)
   public void oldestFunc_null(CacheContext context,
@@ -440,6 +445,7 @@ public void oldestFunc_null(CacheContext context,
     assertThrows(NullPointerException.class, () -> expireAfterAccess.oldest(null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE)
   public void oldestFunc_nullResult(CacheContext context,
@@ -582,6 +588,7 @@ public void youngest_snapshot(Cache<Int, Int> cache, CacheContext context,
     assertThat(youngest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE)
   public void youngestFunc_null(CacheContext context,
@@ -589,6 +596,7 @@ public void youngestFunc_null(CacheContext context,
     assertThrows(NullPointerException.class, () -> expireAfterAccess.youngest(null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE)
   public void youngestFunc_nullResult(CacheContext context,
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java
index d447b056c6..8c2579cf71 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java
@@ -34,6 +34,7 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -57,6 +58,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiFunction;
 
+import org.jspecify.annotations.Nullable;
 import org.mockito.Mockito;
 import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
@@ -961,6 +963,7 @@ public void setExpiresAfter_expired(Cache<Int, Int> cache,
   /* --------------- Policy: putIfAbsent --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -971,6 +974,7 @@ public void putIfAbsent_nullKey(Cache<Int, Int> cache,
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -981,6 +985,7 @@ public void putIfAbsent_nullValue(Cache<Int, Int> cache,
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -1001,6 +1006,7 @@ public void putIfAbsent_negativeDuration(Cache<Int, Int> cache,
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -1062,6 +1068,7 @@ public void putIfAbsent_present(Cache<Int, Int> cache,
   /* --------------- Policy: put --------------- */
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -1072,6 +1079,7 @@ public void put_nullKey(Cache<Int, Int> cache,
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -1082,6 +1090,7 @@ public void put_nullValue(Cache<Int, Int> cache,
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -1115,6 +1124,7 @@ public void put_excessiveDuration(Cache<Int, Int> cache,
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(population = Population.FULL,
       expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE)
@@ -1162,6 +1172,7 @@ public void put_replace(Cache<Int, Int> cache,
 
   /* --------------- Policy: compute --------------- */
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expiry = CacheExpiry.ACCESS, removalListener = {Listener.DISABLED, Listener.REJECTING})
   public void compute_nullKey(CacheContext context, VarExpiration<Int, Int> expireAfterVar) {
@@ -1170,6 +1181,7 @@ public void compute_nullKey(CacheContext context, VarExpiration<Int, Int> expire
   }
 
   @CheckNoStats
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expiry = CacheExpiry.ACCESS, removalListener = {Listener.DISABLED, Listener.REJECTING})
   public void compute_nullMappingFunction(CacheContext context,
@@ -1227,8 +1239,8 @@ public void compute_remove(Cache<Int, Int> cache,
   @SuppressWarnings("CheckReturnValue")
   @CacheSpec(expiry = CacheExpiry.ACCESS)
   public void compute_recursive(CacheContext context, VarExpiration<Int, Int> expireAfterVar) {
-    var mappingFunction = new BiFunction<Int, Int, Int>() {
-      @Override public Int apply(Int key, Int value) {
+    var mappingFunction = new BiFunction<Int, Int, @Nullable Int>() {
+      @Override public @Nullable Int apply(Int key, Int value) {
         return expireAfterVar.compute(key, this, Duration.ofDays(1));
       }
     };
@@ -1243,8 +1255,8 @@ public void compute_recursive(CacheContext context, VarExpiration<Int, Int> expi
   public void compute_pingpong(CacheContext context, VarExpiration<Int, Int> expireAfterVar) {
     var key1 = Int.valueOf(1);
     var key2 = Int.valueOf(2);
-    var mappingFunction = new BiFunction<Int, Int, Int>() {
-      @Override public Int apply(Int key, Int value) {
+    var mappingFunction = new BiFunction<Int, Int, @Nullable Int>() {
+      @Override public @Nullable Int apply(Int key, Int value) {
         return expireAfterVar.compute(key.equals(key1) ? key2 : key1, this, Duration.ofDays(1));
       }
     };
@@ -1416,7 +1428,7 @@ public void compute_differentValue(Cache<Int, Int> cache,
     var replaced = new HashMap<Int, Int>();
     var duration = context.expiryTime().duration().dividedBy(2);
     for (Int key : context.firstMiddleLastKeys()) {
-      Int value = context.original().get(key);
+      Int value = requireNonNull(context.original().get(key));
       Int result = expireAfterVar.compute(key, (k, v) -> value.negate(), duration);
 
       replaced.put(key, value);
@@ -1679,12 +1691,14 @@ public void oldest_snapshot(Cache<Int, Int> cache,
     assertThat(oldest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expiry = CacheExpiry.ACCESS)
   public void oldestFunc_null(CacheContext context, VarExpiration<Int, Int> expireAfterVar) {
     assertThrows(NullPointerException.class, () -> expireAfterVar.oldest(null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expiry = CacheExpiry.ACCESS)
   public void oldestFunc_nullResult(CacheContext context, VarExpiration<Int, Int> expireAfterVar) {
@@ -1819,12 +1833,14 @@ public void youngest_snapshot(Cache<Int, Int> cache,
     assertThat(youngest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expiry = CacheExpiry.ACCESS)
   public void youngestFunc_null(CacheContext context, VarExpiration<Int, Int> expireAfterVar) {
     assertThrows(NullPointerException.class, () -> expireAfterVar.youngest(null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expiry = CacheExpiry.ACCESS)
   public void youngestFunc_nullResult(CacheContext context,
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java
index 2f3ee7914f..a13a8c190b 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java
@@ -27,6 +27,7 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static org.junit.Assert.assertThrows;
 import static org.slf4j.event.Level.WARN;
 
@@ -94,7 +95,7 @@ public void getIfPresent(Cache<Int, Int> cache, CacheContext context) {
       mustExpireWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE,
       expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE)
   public void get(Cache<Int, Int> cache, CacheContext context) {
-    Function<Int, Int> mappingFunction = context.original()::get;
+    Function<Int, Int> mappingFunction = key -> requireNonNull(context.original().get(key));
     context.ticker().advance(Duration.ofSeconds(30));
     var value = cache.get(context.firstKey(), mappingFunction);
     assertThat(value).isEqualTo(context.original().get(context.firstKey()));
@@ -135,14 +136,14 @@ public void getAllPresent(Cache<Int, Int> cache, CacheContext context) {
   public void getAll(Cache<Int, Int> cache, CacheContext context) {
     context.ticker().advance(Duration.ofSeconds(30));
     var result1 = cache.getAll(List.of(context.firstKey(), context.absentKey()),
-        keys -> Maps.asMap(keys, identity()));
+        keys -> Maps.toMap(keys, identity()));
     assertThat(result1).containsExactly(
         context.firstKey(), context.firstKey().negate(), context.absentKey(), context.absentKey());
 
     context.ticker().advance(Duration.ofSeconds(45));
     cache.cleanUp();
     var result2 = cache.getAll(List.of(context.firstKey(), context.absentKey()),
-        keys -> Maps.asMap(keys, identity()));
+        keys -> Maps.toMap(keys, identity()));
     assertThat(result2).containsExactly(
         context.firstKey(), context.firstKey(), context.absentKey(), context.absentKey());
     assertThat(cache).hasSize(2);
@@ -395,6 +396,7 @@ public void oldest_snapshot(Cache<Int, Int> cache, CacheContext context,
     assertThat(oldest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE)
   public void oldestFunc_null(CacheContext context,
@@ -406,6 +408,7 @@ public void oldestFunc_null(CacheContext context,
   @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE)
   public void oldestFunc_nullResult(CacheContext context,
       @ExpireAfterWrite FixedExpiration<Int, Int> expireAfterWrite) {
+    @SuppressWarnings("NullAway")
     var result = expireAfterWrite.oldest(stream -> null);
     assertThat(result).isNull();
   }
@@ -544,6 +547,7 @@ public void youngest_snapshot(Cache<Int, Int> cache, CacheContext context,
     assertThat(youngest).containsExactlyEntriesIn(context.original());
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE)
   public void youngestFunc_null(CacheContext context,
@@ -555,6 +559,7 @@ public void youngestFunc_null(CacheContext context,
   @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE)
   public void youngestFunc_nullResult(CacheContext context,
       @ExpireAfterWrite FixedExpiration<Int, Int> expireAfterWrite) {
+    @SuppressWarnings("NullAway")
     var result = expireAfterWrite.youngest(stream -> null);
     assertThat(result).isNull();
   }
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java
index 662caaa84c..c72e9fad5f 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java
@@ -65,6 +65,7 @@ public static TestSuite suite() {
         .createTestSuite();
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "interners")
   public void intern_null(Interner<Int> interner) {
     assertThrows(NullPointerException.class, () -> interner.intern(null));
@@ -143,6 +144,7 @@ public void nullPointerExceptions() {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void factory() {
     assertThat(Interned.FACTORY.newReferenceKey(new Object(), null))
         .isInstanceOf(WeakKeyEqualsReference.class);
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java
index 209b281e48..f1766c5e76 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java
@@ -16,9 +16,12 @@
 package com.github.benmanes.caffeine.cache;
 
 import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
 
 import java.util.Iterator;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.testing.CollectionSubject;
 import com.google.common.collect.Sets;
 import com.google.common.truth.FailureMetadata;
@@ -32,9 +35,10 @@ final class LinkedDequeSubject extends CollectionSubject {
   private final LinkedDeque<Object> actual;
 
   @SuppressWarnings("unchecked")
-  private LinkedDequeSubject(FailureMetadata metadata, LinkedDeque<?> subject) {
+  private LinkedDequeSubject(FailureMetadata metadata, @Nullable LinkedDeque<?> subject) {
     super(metadata, subject);
-    this.actual = (LinkedDeque<Object>) subject;
+    this.actual = requireNonNull((LinkedDeque<Object>) subject);
+
   }
 
   public static Factory<LinkedDequeSubject, LinkedDeque<?>> deque() {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java
index 20eabf84f2..a5e8b2d8db 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java
@@ -18,6 +18,7 @@
 import static com.github.benmanes.caffeine.testing.CollectionSubject.assertThat;
 import static com.google.common.collect.Iterators.elementsEqual;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static org.junit.Assert.assertThrows;
 
 import java.util.ArrayList;
@@ -29,6 +30,7 @@
 import java.util.NoSuchElementException;
 import java.util.concurrent.ThreadLocalRandom;
 
+import org.jspecify.annotations.Nullable;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -115,7 +117,9 @@ public void moveToFront_last(LinkedDeque<LinkedValue> deque) {
     checkMoveToFront(deque, deque.getLast());
   }
 
-  private static void checkMoveToFront(LinkedDeque<LinkedValue> deque, LinkedValue element) {
+  private static void checkMoveToFront(LinkedDeque<LinkedValue> deque,
+      @Nullable LinkedValue element) {
+    assertThat(element).isNotNull();
     deque.moveToFront(element);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.peekFirst()).isEqualTo(element);
@@ -136,7 +140,9 @@ public void moveToBack_last(LinkedDeque<LinkedValue> deque) {
     checkMoveToBack(deque, deque.getLast());
   }
 
-  private static void checkMoveToBack(LinkedDeque<LinkedValue> deque, LinkedValue element) {
+  private static void checkMoveToBack(LinkedDeque<LinkedValue> deque,
+      @Nullable LinkedValue element) {
+    assertThat(element).isNotNull();
     deque.moveToBack(element);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.getLast()).isEqualTo(element);
@@ -152,7 +158,7 @@ public void isFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void isFirst_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var first = deque.first;
+    var first = requireNonNull(deque.first);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.isFirst(first)).isTrue();
     assertThat(deque.contains(first)).isTrue();
@@ -167,7 +173,7 @@ public void isLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void isLast_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var last = deque.last;
+    var last = requireNonNull(deque.last);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.isLast(last)).isTrue();
     assertThat(deque.contains(last)).isTrue();
@@ -183,7 +189,7 @@ public void peek_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void peek_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var first = deque.first;
+    var first = requireNonNull(deque.first);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.contains(first)).isTrue();
     assertThat(deque.first).isSameInstanceAs(first);
@@ -197,7 +203,7 @@ public void peekFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void peekFirst_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var first = deque.first;
+    var first = requireNonNull(deque.first);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.contains(first)).isTrue();
     assertThat(deque.first).isSameInstanceAs(first);
@@ -211,7 +217,7 @@ public void peekLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void peekLast_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var last = deque.last;
+    var last = requireNonNull(deque.last);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.contains(last)).isTrue();
     assertThat(deque.last).isSameInstanceAs(last);
@@ -227,7 +233,7 @@ public void getFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void getFirst_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var first = deque.first;
+    var first = requireNonNull(deque.first);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.contains(first)).isTrue();
     assertThat(deque.first).isSameInstanceAs(first);
@@ -241,7 +247,7 @@ public void getLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void getLast_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var last = deque.last;
+    var last = requireNonNull(deque.last);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.contains(last)).isTrue();
     assertThat(deque.last).isSameInstanceAs(last);
@@ -257,7 +263,7 @@ public void element_whenEmpty(LinkedDeque<LinkedValue> deque) {
 
   @Test(dataProvider = "full")
   public void element_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
-    var first = deque.first;
+    var first = requireNonNull(deque.first);
     assertThat(deque).hasSize(SIZE);
     assertThat(deque.contains(first)).isTrue();
     assertThat(deque.first).isSameInstanceAs(first);
@@ -862,34 +868,34 @@ void populate(Collection<LinkedValue> collection) {
   static final class LinkedValue implements AccessOrder<LinkedValue>, WriteOrder<LinkedValue> {
     final int value;
 
-    LinkedValue prev;
-    LinkedValue next;
+    @Nullable LinkedValue prev;
+    @Nullable LinkedValue next;
 
     LinkedValue(int value) {
       this.value = value;
     }
-    @Override public LinkedValue getPreviousInAccessOrder() {
+    @Override public @Nullable LinkedValue getPreviousInAccessOrder() {
       return prev;
     }
-    @Override public void setPreviousInAccessOrder(LinkedValue prev) {
+    @Override public void setPreviousInAccessOrder(@Nullable LinkedValue prev) {
       this.prev = prev;
     }
-    @Override public LinkedValue getNextInAccessOrder() {
+    @Override public @Nullable LinkedValue getNextInAccessOrder() {
       return next;
     }
-    @Override public void setNextInAccessOrder(LinkedValue next) {
+    @Override public void setNextInAccessOrder(@Nullable LinkedValue next) {
       this.next = next;
     }
-    @Override public LinkedValue getPreviousInWriteOrder() {
+    @Override public @Nullable LinkedValue getPreviousInWriteOrder() {
       return prev;
     }
-    @Override public void setPreviousInWriteOrder(LinkedValue prev) {
+    @Override public void setPreviousInWriteOrder(@Nullable LinkedValue prev) {
       this.prev = prev;
     }
-    @Override public LinkedValue getNextInWriteOrder() {
+    @Override public @Nullable LinkedValue getNextInWriteOrder() {
       return next;
     }
-    @Override public void setNextInWriteOrder(LinkedValue next) {
+    @Override public void setNextInWriteOrder(@Nullable LinkedValue next) {
       this.next = next;
     }
     @Override public boolean equals(Object o) {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTests.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTests.java
index 38c579cf53..406319bb44 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTests.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTests.java
@@ -128,7 +128,7 @@ static final class LinkedValue implements AccessOrder<LinkedValue>, WriteOrder<L
     }
 
     @Override
-    public LinkedValue getPreviousInAccessOrder() {
+    public @Nullable LinkedValue getPreviousInAccessOrder() {
       return prev;
     }
 
@@ -138,7 +138,7 @@ public void setPreviousInAccessOrder(@Nullable LinkedValue prev) {
     }
 
     @Override
-    public LinkedValue getNextInAccessOrder() {
+    public @Nullable LinkedValue getNextInAccessOrder() {
       return next;
     }
 
@@ -148,22 +148,22 @@ public void setNextInAccessOrder(@Nullable LinkedValue next) {
     }
 
     @Override
-    public LinkedValue getPreviousInWriteOrder() {
+    public @Nullable LinkedValue getPreviousInWriteOrder() {
       return prev;
     }
 
     @Override
-    public void setPreviousInWriteOrder(LinkedValue prev) {
+    public void setPreviousInWriteOrder(@Nullable LinkedValue prev) {
       this.prev = prev;
     }
 
     @Override
-    public LinkedValue getNextInWriteOrder() {
+    public @Nullable LinkedValue getNextInWriteOrder() {
       return next;
     }
 
     @Override
-    public void setNextInWriteOrder(LinkedValue next) {
+    public void setNextInWriteOrder(@Nullable LinkedValue next) {
       this.next = next;
     }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java
index 96252ab64a..8d5b26975f 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java
@@ -33,6 +33,7 @@
 import static com.github.benmanes.caffeine.testing.MapSubject.assertThat;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.Assert.assertThrows;
 import static org.slf4j.event.Level.TRACE;
@@ -96,6 +97,7 @@ public final class LoadingCacheTest {
   /* --------------- get --------------- */
 
   @CacheSpec
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CheckNoEvictions @CheckNoStats
   public void get_null(LoadingCache<Int, Int> cache, CacheContext context) {
@@ -160,6 +162,7 @@ public void get_present(LoadingCache<Int, Int> cache, CacheContext context) {
   /* --------------- getAll --------------- */
 
   @CheckNoEvictions
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void getAll_iterable_null(LoadingCache<Int, Int> cache, CacheContext context) {
@@ -433,13 +436,14 @@ final class Key {
         return 0; // to put keys in one bucket
       }
     }
+    @SuppressWarnings("NullAway")
     LoadingCache<Object, Int> cache = context.build(key -> null);
 
     var keys = intern(new ArrayList<Key>());
     for (int i = 0; i < Population.FULL.size(); i++) {
       keys.add(new Key());
     }
-    Key key = Iterables.getLast(keys);
+    Key key = requireNonNull(Iterables.getLast(keys));
     Int value = context.absentValue();
     cache.put(key, value);
 
@@ -451,6 +455,7 @@ final class Key {
   /* --------------- refresh --------------- */
 
   @CheckNoEvictions
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "caches")
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void refresh_null(LoadingCache<Int, Int> cache, CacheContext context) {
@@ -958,6 +963,7 @@ public void refresh_nullFuture_load(CacheContext context) {
       @Override public Int load(Int key) {
         throw new IllegalStateException();
       }
+      @SuppressWarnings("NullAway")
       @Override public CompletableFuture<Int> asyncLoad(Int key, Executor executor) {
         return null;
       }
@@ -972,6 +978,7 @@ public void refresh_nullFuture_reload(CacheContext context) {
       @Override public Int load(Int key) {
         throw new IllegalStateException();
       }
+      @SuppressWarnings("NullAway")
       @Override public CompletableFuture<Int> asyncReload(
           Int key, Int oldValue, Executor executor) {
         return null;
@@ -984,6 +991,7 @@ public void refresh_nullFuture_reload(CacheContext context) {
   /* --------------- refreshAll --------------- */
 
   @Test(dataProvider = "caches")
+  @SuppressWarnings("NullAway")
   @CheckNoEvictions @CheckNoStats
   @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING })
   public void refreshAll_null(LoadingCache<Int, Int> cache, CacheContext context) {
@@ -1085,6 +1093,7 @@ public void refreshAll_nullFuture_load(CacheContext context) {
       @Override public Int load(Int key) {
         throw new IllegalStateException();
       }
+      @SuppressWarnings("NullAway")
       @Override public CompletableFuture<Int> asyncLoad(Int key, Executor executor) {
         return null;
       }
@@ -1099,6 +1108,7 @@ public void refreshAll_nullFuture_reload(CacheContext context) {
       @Override public Int load(Int key) {
         throw new IllegalStateException();
       }
+      @SuppressWarnings("NullAway")
       @Override public CompletableFuture<Int> asyncReload(
           Int key, Int oldValue, Executor executor) {
         return null;
@@ -1112,7 +1122,7 @@ public void refreshAll_nullFuture_reload(CacheContext context) {
 
   @Test
   public void loadAll() {
-    CacheLoader<Object, ?> loader = key -> key;
+    CacheLoader<Object, Object> loader = key -> key;
     assertThrows(UnsupportedOperationException.class, () -> loader.loadAll(Set.of()));
   }
 
@@ -1153,7 +1163,7 @@ public void asyncLoadAll_exception() throws Exception {
 
   @Test
   public void asyncLoadAll() throws Throwable {
-    CacheLoader<Object, ?> loader = key -> key;
+    CacheLoader<Object, Object> loader = key -> key;
     assertThat(loader.asyncLoadAll(Set.of(), Runnable::run))
         .failsWith(CompletionException.class)
         .hasCauseThat().isInstanceOf(UnsupportedOperationException.class);
@@ -1176,6 +1186,7 @@ public void asyncReload() throws Exception {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   public void bulk_null() {
     assertThrows(NullPointerException.class, () -> CacheLoader.bulk(null));
   }
@@ -1201,9 +1212,9 @@ public void bulk_present() throws Exception {
   @Test(dataProvider = "caches")
   @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.ASYNC_INCOMPLETE)
   public void refreshes(LoadingCache<Int, Int> cache, CacheContext context) {
-    var key1 = Iterables.get(context.absentKeys(), 0);
+    var key1 = requireNonNull(Iterables.get(context.absentKeys(), 0));
     var key2 = context.original().isEmpty()
-        ? Iterables.get(context.absentKeys(), 1)
+        ? requireNonNull(Iterables.get(context.absentKeys(), 1))
         : context.firstKey();
     var future1 = cache.refresh(key1);
     var future2 = cache.refresh(key2);
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java
index e4163ad913..13890f5654 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java
@@ -19,6 +19,7 @@
 import static com.github.benmanes.caffeine.testing.Awaits.await;
 import static com.github.benmanes.caffeine.testing.MapSubject.map;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 
 import java.util.Collection;
 import java.util.Map;
@@ -26,6 +27,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.cache.Async.AsyncWeigher;
 import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalAsyncCache;
 import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalAsyncLoadingCache;
@@ -38,7 +41,7 @@
 import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalAsyncLoadingCache;
 import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalManualCache;
 import com.github.benmanes.caffeine.cache.stats.StatsCounter;
-import com.github.benmanes.caffeine.cache.testing.Weighers;
+import com.github.benmanes.caffeine.cache.testing.Weighers.SkippedWeigher;
 import com.google.common.collect.ImmutableTable;
 import com.google.common.collect.Sets;
 import com.google.common.truth.FailureMetadata;
@@ -54,9 +57,9 @@
 public final class LocalCacheSubject extends Subject {
   private final Object actual;
 
-  private LocalCacheSubject(FailureMetadata metadata, Object subject) {
+  private LocalCacheSubject(FailureMetadata metadata, @Nullable Object subject) {
     super(metadata, subject);
-    this.actual = subject;
+    this.actual = requireNonNull(subject);
   }
 
   public static Factory<LocalCacheSubject, AsyncCache<?, ?>> asyncLocal() {
@@ -298,11 +301,12 @@ private void checkLinks(BoundedLocalCache<Object, Object> bounded,
     long totalWeightedSize = 0;
     Set<Node<Object, Object>> seen = Sets.newIdentityHashSet();
     for (var cell : deques.cellSet()) {
-      long weightedSize = scanLinks(bounded, cell.getValue(), seen);
-      check("%s: %s in %s", cell.getRowKey(), cell.getValue(), bounded.data)
+      var deque = requireNonNull(cell.getValue());
+      long weightedSize = scanLinks(bounded, deque, seen);
+      check("%s: %s in %s", cell.getRowKey(), deque, bounded.data)
           .that(weightedSize).isEqualTo(cell.getColumnKey());
-      totalSize += cell.getValue().size();
       totalWeightedSize += weightedSize;
+      totalSize += deque.size();
     }
     check("linkSize").withMessage("cache.size() != links").that(bounded).hasSize(seen.size());
     check("totalSize").withMessage("cache.size() == deque.size()").that(bounded).hasSize(totalSize);
@@ -354,7 +358,7 @@ private void checkNode(BoundedLocalCache<Object, Object> bounded, Node<Object, O
   }
 
   private void checkKey(BoundedLocalCache<Object, Object> bounded,
-      Node<Object, Object> node, Object key, Object value) {
+      Node<Object, Object> node, @Nullable Object key, @Nullable Object value) {
     if (bounded.collectKeys()) {
       if ((key != null) && (value != null)) {
         check("bounded").that(bounded).containsKey(key);
@@ -368,10 +372,11 @@ private void checkKey(BoundedLocalCache<Object, Object> bounded,
   }
 
   private void checkValue(BoundedLocalCache<Object, Object> bounded,
-      Node<Object, Object> node, Object key, Object value) {
+      Node<Object, Object> node, @Nullable Object key, @Nullable Object value) {
     if (!bounded.collectValues()) {
       check("value").that(value).isNotNull();
       if ((key != null) && !bounded.hasExpired(node, bounded.expirationTicker().read())) {
+        requireNonNull(value);
         check("containsValue(value) for key %s", key)
             .about(map()).that(bounded).containsValue(value);
       }
@@ -379,7 +384,7 @@ private void checkValue(BoundedLocalCache<Object, Object> bounded,
     checkIfAsyncValue(value);
   }
 
-  private void checkIfAsyncValue(Object value) {
+  private void checkIfAsyncValue(@Nullable Object value) {
     if (value instanceof CompletableFuture<?>) {
       var future = (CompletableFuture<?>) value;
       if (!future.isDone() || future.isCompletedExceptionally()) {
@@ -390,18 +395,22 @@ private void checkIfAsyncValue(Object value) {
   }
 
   private void checkWeight(BoundedLocalCache<Object, Object> bounded,
-      Node<Object, Object> node, Object key, Object value) {
+      Node<Object, Object> node, @Nullable Object key, @Nullable Object value) {
     check("node.getWeight").that(node.getWeight()).isAtLeast(0);
+    if ((key == null) || (value == null)) {
+      return;
+    }
 
-    var weigher = bounded.weigher;
-    boolean canCheckWeight = (weigher == Weighers.random());
+    Weigher<?, ?> weigher = bounded.weigher;
     if (weigher instanceof AsyncWeigher) {
-      @SuppressWarnings("rawtypes")
-      var asyncWeigher = (AsyncWeigher) weigher;
-      canCheckWeight = (asyncWeigher.delegate == Weighers.random());
+      weigher = ((AsyncWeigher<?, ?>) weigher).delegate;
+    }
+    if (weigher instanceof BoundedWeigher) {
+      weigher = ((BoundedWeigher<?, ?>) weigher).delegate;
     }
-    if (canCheckWeight) {
-      check("node.getWeight()").that(node.getWeight()).isEqualTo(weigher.weigh(key, value));
+    if (!(weigher instanceof SkippedWeigher)) {
+      int weight = bounded.weigher.weigh(key, value);
+      check("node.getWeight()").that(node.getWeight()).isEqualTo(weight);
     }
   }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java
index 550f1e0f40..8d76b4c9a1 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.STRICT_STUBS;
 
 import java.util.Random;
 import java.util.concurrent.CompletableFuture;
@@ -28,8 +29,10 @@
 import java.util.concurrent.TimeUnit;
 
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.testng.MockitoSettings;
+import org.mockito.testng.MockitoTestNGListener;
 import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
 
 import com.google.common.primitives.Ints;
@@ -38,6 +41,8 @@
  * @author ben.manes@gmail.com (Ben Manes)
  */
 @Test(singleThreaded = true)
+@Listeners(MockitoTestNGListener.class)
+@MockitoSettings(strictness = STRICT_STUBS)
 public final class PacerTest {
   private static final long ONE_MINUTE_IN_NANOS = TimeUnit.MINUTES.toNanos(1);
   private static final Random random = new Random();
@@ -51,8 +56,7 @@ public final class PacerTest {
   Pacer pacer;
 
   @BeforeMethod
-  public void beforeMethod() throws Exception {
-    MockitoAnnotations.openMocks(this).close();
+  public void beforeMethod() {
     pacer = new Pacer(scheduler);
   }
 
@@ -210,7 +214,7 @@ public void isScheduled_nullFuture() {
 
   @Test
   public void isScheduled_doneFuture() {
-    pacer.future = DisabledFuture.INSTANCE;
+    pacer.future = DisabledFuture.instance();
     assertThat(pacer.isScheduled()).isFalse();
   }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java
index 253b11f3c6..6a75f03686 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java
@@ -30,6 +30,7 @@
 import static com.google.common.base.Predicates.not;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -204,8 +205,8 @@ public void getAll(Cache<Int, Int> cache, CacheContext context) {
 
     context.clear();
     GcFinalization.awaitFullGc();
-    assertThat(cache.getAll(keys, keysToLoad -> Maps.asMap(keysToLoad, Int::negate)))
-        .containsExactlyEntriesIn(Maps.asMap(keys, Int::negate));
+    assertThat(cache.getAll(keys, keysToLoad -> Maps.toMap(keysToLoad, Int::negate)))
+        .containsExactlyEntriesIn(Maps.toMap(keys, Int::negate));
 
     assertThat(cache).whenCleanedUp().hasSize(keys.size());
     assertThat(context).notifications().withCause(COLLECTED)
@@ -299,7 +300,8 @@ public void invalidateAll_iterable(Cache<Int, Int> cache, CacheContext context)
     List<Map.Entry<Int, Int>> collected;
     var keys = context.firstMiddleLastKeys();
     if (context.isStrongValues()) {
-      retained = Maps.toMap(context.firstMiddleLastKeys(), key -> context.original().get(key));
+      retained = Maps.toMap(context.firstMiddleLastKeys(),
+          key -> requireNonNull(context.original().get(key)));
       collected = getExpectedAfterGc(context,
           Maps.filterKeys(context.original(), not(keys::contains)));
     } else {
@@ -335,7 +337,8 @@ public void invalidateAll_full(Cache<Int, Int> cache, CacheContext context) {
     List<Map.Entry<Int, Int>> collected = getExpectedAfterGc(context,
         Maps.filterKeys(context.original(), not(keys::contains)));
     if (context.isStrongValues()) {
-      retained = Maps.toMap(context.firstMiddleLastKeys(), key -> context.original().get(key));
+      retained = Maps.toMap(context.firstMiddleLastKeys(),
+          key -> requireNonNull(context.original().get(key)));
     } else {
       retained = Map.of();
       for (var key : keys) {
@@ -428,7 +431,7 @@ public void getAll_loading(LoadingCache<Int, Int> cache, CacheContext context) {
 
     context.clear();
     GcFinalization.awaitFullGc();
-    assertThat(cache.getAll(keys)).containsExactlyEntriesIn(Maps.asMap(keys, Int::negate));
+    assertThat(cache.getAll(keys)).containsExactlyEntriesIn(Maps.toMap(keys, Int::negate));
     assertThat(cache).whenCleanedUp().hasSize(keys.size());
 
     assertThat(context).notifications().withCause(COLLECTED)
@@ -504,8 +507,8 @@ public void getAll_async(AsyncCache<Int, Int> cache, CacheContext context) {
 
     context.clear();
     GcFinalization.awaitFullGc();
-    assertThat(cache.getAll(keys, keysToLoad -> Maps.asMap(keysToLoad, Int::negate)).join())
-        .containsExactlyEntriesIn(Maps.asMap(keys, Int::negate));
+    assertThat(cache.getAll(keys, keysToLoad -> Maps.toMap(keysToLoad, Int::negate)).join())
+        .containsExactlyEntriesIn(Maps.toMap(keys, Int::negate));
     assertThat(context).notifications().withCause(COLLECTED)
         .contains(collected).exclusively();
   }
@@ -589,7 +592,8 @@ public void containsValue(Map<Int, Int> map, CacheContext context) {
       maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED,
       stats = Stats.ENABLED, removalListener = Listener.CONSUMING)
   public void clear(Map<Int, Int> map, CacheContext context) {
-    var retained = Maps.toMap(context.firstMiddleLastKeys(), key -> context.original().get(key));
+    var retained = Maps.toMap(context.firstMiddleLastKeys(),
+        key -> requireNonNull(context.original().get(key)));
     var collected = getExpectedAfterGc(context, Maps.difference(
         context.original(), retained).entriesOnlyOnLeft());
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java
index 2be25f8e83..836a45a4c7 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java
@@ -30,6 +30,7 @@
 import static com.github.benmanes.caffeine.testing.LoggingEvents.logEvents;
 import static com.github.benmanes.caffeine.testing.MapSubject.assertThat;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertThrows;
@@ -230,7 +231,7 @@ public void refreshIfNeeded_replaced(LoadingCache<Int, Int> cache, CacheContext
     assertThat(value).isNotNull();
 
     assertThat(cache.policy().refreshes()).isNotEmpty();
-    var future = cache.policy().refreshes().get(context.firstKey());
+    var future = requireNonNull(cache.policy().refreshes().get(context.firstKey()));
 
     cache.put(context.firstKey(), context.absentValue());
     future.complete(context.absentKey().negate());
@@ -256,7 +257,7 @@ public void refreshIfNeeded_expired(LoadingCache<Int, Int> cache, CacheContext c
     assertThat(value).isNotNull();
 
     assertThat(cache.policy().refreshes()).isNotEmpty();
-    var future = cache.policy().refreshes().get(context.firstKey());
+    var future = requireNonNull(cache.policy().refreshes().get(context.firstKey()));
 
     context.ticker().advance(Duration.ofMinutes(1));
     future.complete(context.absentValue());
@@ -280,7 +281,7 @@ public void refreshIfNeeded_absent_newValue(LoadingCache<Int, Int> cache, CacheC
     assertThat(value).isNotNull();
 
     assertThat(cache.policy().refreshes()).isNotEmpty();
-    var future = cache.policy().refreshes().get(context.firstKey());
+    var future = requireNonNull(cache.policy().refreshes().get(context.firstKey()));
 
     cache.invalidate(context.firstKey());
     assertThat(cache).doesNotContainKey(context.firstKey());
@@ -306,7 +307,7 @@ public void refreshIfNeeded_absent_nullValue(LoadingCache<Int, Int> cache, Cache
     assertThat(value).isNotNull();
 
     assertThat(cache.policy().refreshes()).isNotEmpty();
-    var future = cache.policy().refreshes().get(context.firstKey());
+    var future = requireNonNull(cache.policy().refreshes().get(context.firstKey()));
 
     cache.invalidate(context.firstKey());
     future.complete(null);
@@ -404,6 +405,7 @@ public void refreshIfNeeded_nullFuture(CacheContext context) {
       @Override public Int load(Int key) {
         throw new IllegalStateException();
       }
+      @SuppressWarnings("NullAway")
       @Override public CompletableFuture<Int> asyncReload(
           Int key, Int oldValue, Executor executor) {
         refreshed.set(true);
@@ -463,7 +465,8 @@ public void getIfPresent_delayed(LoadingCache<Int, Int> cache, CacheContext cont
     assertThat(context).removalNotifications().isEmpty();
 
     if (context.isCaffeine()) {
-      cache.policy().refreshes().get(context.middleKey()).complete(context.middleKey());
+      var future = requireNonNull(cache.policy().refreshes().get(context.middleKey()));
+      future.complete(context.middleKey());
       assertThat(context).removalNotifications().withCause(REPLACED)
           .contains(context.middleKey(), context.original().get(context.middleKey()))
           .exclusively();
@@ -499,7 +502,7 @@ public void getAllPresent_immediate(LoadingCache<Int, Int> cache, CacheContext c
     assertThat(results).isNotEmpty();
     context.ticker().advance(Duration.ofSeconds(45));
     assertThat(cache.getAllPresent(context.firstMiddleLastKeys()))
-        .containsExactlyEntriesIn(Maps.asMap(context.firstMiddleLastKeys(), key -> key));
+        .containsExactlyEntriesIn(Maps.toMap(context.firstMiddleLastKeys(), key -> key));
 
     assertThat(cache).hasSize(context.initialSize());
     assertThat(context).removalNotifications().withCause(REPLACED)
@@ -521,7 +524,8 @@ public void getAllPresent_delayed(LoadingCache<Int, Int> cache, CacheContext con
     if (context.isCaffeine()) {
       var replaced = new HashMap<Int, Int>();
       for (var key : context.firstMiddleLastKeys()) {
-        cache.policy().refreshes().get(key).complete(key);
+        var future = requireNonNull(cache.policy().refreshes().get(key));
+        future.complete(key);
         replaced.put(key, context.original().get(key));
       }
       assertThat(context).removalNotifications().withCause(REPLACED)
@@ -554,7 +558,7 @@ public void getFunc_immediate(LoadingCache<Int, Int> cache, CacheContext context
   @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.ASYNC_INCOMPLETE,
       population = { Population.PARTIAL, Population.FULL })
   public void getFunc_delayed(LoadingCache<Int, Int> cache, CacheContext context) {
-    Function<Int, Int> mappingFunction = context.original()::get;
+    Function<Int, Int> mappingFunction = key -> requireNonNull(context.original().get(key));
     context.ticker().advance(Duration.ofSeconds(30));
     var value = cache.get(context.firstKey(), mappingFunction);
     assertThat(value).isNotNull();
@@ -563,7 +567,8 @@ public void getFunc_delayed(LoadingCache<Int, Int> cache, CacheContext context)
     assertThat(cache.get(context.lastKey(), mappingFunction)).isEqualTo(context.lastKey().negate());
 
     if (context.isCaffeine()) {
-      cache.policy().refreshes().get(context.lastKey()).complete(context.lastKey());
+      var future = requireNonNull(cache.policy().refreshes().get(context.lastKey()));
+      future.complete(context.lastKey());
       assertThat(context).removalNotifications().withCause(REPLACED)
           .contains(context.lastKey(), context.original().get(context.lastKey()))
           .exclusively();
@@ -575,7 +580,7 @@ public void getFunc_delayed(LoadingCache<Int, Int> cache, CacheContext context)
   @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE,
       population = { Population.PARTIAL, Population.FULL })
   public void getFunc_async(AsyncLoadingCache<Int, Int> cache, CacheContext context) {
-    Function<Int, Int> mappingFunction = context.original()::get;
+    Function<Int, Int> mappingFunction = key -> requireNonNull(context.original().get(key));
     context.ticker().advance(Duration.ofSeconds(30));
     cache.get(context.firstKey(), mappingFunction).join();
     context.ticker().advance(Duration.ofSeconds(45));
@@ -620,7 +625,8 @@ public void get_delayed(LoadingCache<Int, Int> cache, CacheContext context) {
     assertThat(cache.get(context.firstKey())).isEqualTo(context.firstKey().negate());
 
     if (context.isCaffeine()) {
-      cache.policy().refreshes().get(context.firstKey()).complete(context.firstKey());
+      var future = requireNonNull(cache.policy().refreshes().get(context.firstKey()));
+      future.complete(context.firstKey());
       assertThat(context).removalNotifications().withCause(REPLACED)
           .contains(context.firstKey(), context.original().get(context.firstKey()))
           .exclusively();
@@ -751,7 +757,8 @@ public void getAll_delayed(LoadingCache<Int, Int> cache, CacheContext context) {
 
     if (context.isCaffeine()) {
       for (var key : keys) {
-        cache.policy().refreshes().get(key).complete(key);
+        var future = requireNonNull(cache.policy().refreshes().get(key));
+        future.complete(key);
       }
       assertThat(context).removalNotifications().withCause(REPLACED)
           .contains(Maps.filterKeys(context.original(), context.firstMiddleLastKeys()::contains))
@@ -915,6 +922,7 @@ public void refreshes(LoadingCache<Int, Int> cache, CacheContext context) {
 
     var future = cache.policy().refreshes().get(context.firstKey());
     assertThat(future).isNotNull();
+    requireNonNull(future);
 
     future.complete(Int.MAX_VALUE);
     assertThat(cache.policy().refreshes()).isExhaustivelyEmpty();
@@ -929,7 +937,7 @@ public void refreshes_nullLookup(LoadingCache<Int, Int> cache, CacheContext cont
     var value = cache.getIfPresent(context.firstKey());
     assertThat(value).isNotNull();
 
-    var future = cache.policy().refreshes().get(context.firstKey());
+    var future = requireNonNull(cache.policy().refreshes().get(context.firstKey()));
     assertThat(cache.policy().refreshes().get(null)).isNull();
     assertThat(cache.policy().refreshes().containsKey(null)).isFalse();
     assertThat(cache.policy().refreshes().containsValue(null)).isFalse();
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java
index 533d351ef9..46de128f79 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java
@@ -17,6 +17,9 @@
 
 import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.asyncCache;
 import static com.github.benmanes.caffeine.cache.testing.CacheSubject.cache;
+import static java.util.Objects.requireNonNull;
+
+import org.jspecify.annotations.Nullable;
 
 import com.github.benmanes.caffeine.cache.Async.AsyncEvictionListener;
 import com.github.benmanes.caffeine.cache.Async.AsyncExpiry;
@@ -39,9 +42,9 @@
 public final class ReserializableSubject extends Subject {
   private final Object actual;
 
-  private ReserializableSubject(FailureMetadata metadata, Object subject) {
+  private ReserializableSubject(FailureMetadata metadata, @Nullable Object subject) {
     super(metadata, subject);
-    this.actual = subject;
+    this.actual = requireNonNull(subject);
   }
 
   public static Factory<ReserializableSubject, AsyncCache<?, ?>> asyncReserializable() {
@@ -257,7 +260,8 @@ private void checkUnboundedLocalCache(
       UnboundedLocalCache<?, ?> original, UnboundedLocalCache<?, ?> copy) {
     check("isRecordingStats").that(copy.isRecordingStats).isEqualTo(original.isRecordingStats);
 
-    if (original.removalListener == null) {
+    if ((original.removalListener == null) || (copy.removalListener == null)) {
+      check("removalListener").that(original.removalListener).isNull();
       check("removalListener").that(copy.removalListener).isNull();
     } else if (copy.removalListener.getClass() != original.removalListener.getClass()) {
       check("removalListener").that(copy.removalListener).isNotNull();
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java
index 5864fd7cc3..8225640a88 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java
@@ -35,9 +35,11 @@
 
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -104,27 +106,28 @@ public void scheduler(Scheduler scheduler) {
   public void disabledScheduler() {
     var future = Scheduler.disabledScheduler()
         .schedule(Runnable::run, () -> {}, 1, TimeUnit.MINUTES);
-    assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE);
+    assertThat(future).isSameInstanceAs(DisabledFuture.instance());
   }
 
   @Test
-  public void disabledFuture() {
-    assertThat(DisabledFuture.INSTANCE.get(0, TimeUnit.SECONDS)).isNull();
-    assertThat(DisabledFuture.INSTANCE.isCancelled()).isFalse();
-    assertThat(DisabledFuture.INSTANCE.cancel(false)).isFalse();
-    assertThat(DisabledFuture.INSTANCE.cancel(true)).isFalse();
-    assertThat(DisabledFuture.INSTANCE.isDone()).isTrue();
-    assertThat(DisabledFuture.INSTANCE.get()).isNull();
+  public void disabledFuture() throws InterruptedException, ExecutionException, TimeoutException {
+    assertThat(DisabledFuture.instance().get(0, TimeUnit.SECONDS)).isNull();
+    assertThat(DisabledFuture.instance().isCancelled()).isFalse();
+    assertThat(DisabledFuture.instance().cancel(false)).isFalse();
+    assertThat(DisabledFuture.instance().cancel(true)).isFalse();
+    assertThat(DisabledFuture.instance().isDone()).isTrue();
+    assertThat(DisabledFuture.instance().get()).isNull();
   }
 
   @Test
   public void disabledFuture_null() {
-    npeTester.testAllPublicInstanceMethods(DisabledFuture.INSTANCE);
+    npeTester.testAllPublicInstanceMethods(DisabledFuture.instance());
   }
 
   /* --------------- guarded --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void guardedScheduler_null() {
     assertThrows(NullPointerException.class, () -> Scheduler.guardedScheduler(null));
   }
@@ -140,7 +143,7 @@ public void guardedScheduler_nullFuture() {
     var future = Scheduler.guardedScheduler(scheduler)
         .schedule(executor, command, 1L, TimeUnit.MINUTES);
     verify(scheduledExecutor).schedule(any(Runnable.class), eq(1L), eq(TimeUnit.MINUTES));
-    assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE);
+    assertThat(future).isSameInstanceAs(DisabledFuture.instance());
   }
 
   @Test
@@ -154,7 +157,7 @@ public void guardedScheduler() {
   public void guardedScheduler_exception() {
     var future = Scheduler.guardedScheduler((r, e, d, u) -> { throw new IllegalStateException(); })
         .schedule(Runnable::run, () -> {}, 1, TimeUnit.MINUTES);
-    assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE);
+    assertThat(future).isSameInstanceAs(DisabledFuture.instance());
     assertThat(logEvents()
         .withMessage("Exception thrown by scheduler; discarded task")
         .withThrowable(IllegalStateException.class)
@@ -166,6 +169,7 @@ public void guardedScheduler_exception() {
   /* --------------- ScheduledExecutorService --------------- */
 
   @Test
+  @SuppressWarnings("NullAway")
   public void scheduledExecutorService_null() {
     assertThrows(NullPointerException.class, () -> Scheduler.forScheduledExecutorService(null));
   }
@@ -180,7 +184,7 @@ public void scheduledExecutorService_schedule() {
 
     var scheduler = Scheduler.forScheduledExecutorService(scheduledExecutor);
     var future = scheduler.schedule(executor, command, 1L, TimeUnit.MINUTES);
-    assertThat(future).isNotSameInstanceAs(DisabledFuture.INSTANCE);
+    assertThat(future).isNotSameInstanceAs(DisabledFuture.instance());
 
     verify(scheduledExecutor).isShutdown();
     verify(scheduledExecutor).schedule(task.capture(), eq(1L), eq(TimeUnit.MINUTES));
@@ -200,7 +204,7 @@ public void scheduledExecutorService_shutdown() {
     when(scheduledExecutor.isShutdown()).thenReturn(true);
     var scheduler = Scheduler.forScheduledExecutorService(scheduledExecutor);
     var future = scheduler.schedule(executor, () -> {}, 1L, TimeUnit.MINUTES);
-    assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE);
+    assertThat(future).isSameInstanceAs(DisabledFuture.instance());
 
     verify(scheduledExecutor).isShutdown();
     verifyNoMoreInteractions(scheduledExecutor);
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java
index f617d5d2ca..cb98552170 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java
@@ -138,7 +138,7 @@ private void status() {
     System.out.printf(US, "Size = %,d (max: %,d)%n",
         local.data.mappingCount(), workload.maxEntries);
     System.out.printf(US, "Lock = [%s%n", StringUtils.substringAfter(evictionLock.toString(), "["));
-    System.out.printf(US, "Pending reloads = %,d%n", local.refreshes.size());
+    System.out.printf(US, "Pending reloads = %,d%n", local.refreshes().size());
     System.out.printf(US, "Pending tasks = %,d%n",
         ForkJoinPool.commonPool().getQueuedSubmissionCount());
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/StripedBufferTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/StripedBufferTest.java
index f345f9f82f..a441ffd4b4 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/StripedBufferTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/StripedBufferTest.java
@@ -82,6 +82,7 @@ public void produce(FakeBuffer<Integer> buffer) {
         Thread.yield();
       }
     });
+    assertThat(buffer.table).isNotNull();
     assertThat(buffer.table.length).isAtMost(MAXIMUM_TABLE_SIZE);
   }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java
index 4141be2b87..76342abddd 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
 
 import java.lang.ref.ReferenceQueue;
 import java.time.Duration;
@@ -44,15 +45,18 @@
 import java.util.stream.IntStream;
 import java.util.stream.LongStream;
 
+import org.jspecify.annotations.NullUnmarked;
 import org.jspecify.annotations.Nullable;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.testng.MockitoSettings;
+import org.mockito.testng.MockitoTestNGListener;
 import org.testng.ITestResult;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
+import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
 
 import com.github.benmanes.caffeine.cache.TimerWheel.Sentinel;
@@ -65,6 +69,8 @@
  * @author ben.manes@gmail.com (Ben Manes)
  */
 @Test(singleThreaded = true)
+@MockitoSettings(strictness = LENIENT)
+@Listeners(MockitoTestNGListener.class)
 @SuppressWarnings({"ClassEscapesDefinedScope", "GuardedBy"})
 public final class TimerWheelTest {
   private static final Random random = new Random();
@@ -76,9 +82,8 @@ public final class TimerWheelTest {
   TimerWheel<Long, Long> timerWheel;
 
   @BeforeMethod
-  public void beforeMethod() throws Exception {
+  public void beforeMethod() {
     Reset.setThreadLocalRandom(random.nextInt(), random.nextInt());
-    MockitoAnnotations.openMocks(this).close();
     timerWheel = new TimerWheel<>();
   }
 
@@ -203,17 +208,13 @@ public void advance_exception() {
 
   @Test(dataProvider = "clock")
   public void getExpirationDelay_empty(long clock) {
-    when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true);
     timerWheel.nanos = clock;
-
     assertThat(timerWheel.getExpirationDelay()).isEqualTo(Long.MAX_VALUE);
   }
 
   @Test(dataProvider = "clock")
   public void getExpirationDelay_firstWheel(long clock) {
-    when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true);
     timerWheel.nanos = clock;
-
     long delay = Duration.ofSeconds(1).toNanos();
     timerWheel.schedule(new Timer(clock + delay));
     assertThat(timerWheel.getExpirationDelay()).isAtMost(SPANS[0]);
@@ -221,9 +222,7 @@ public void getExpirationDelay_firstWheel(long clock) {
 
   @Test(dataProvider = "clock")
   public void getExpirationDelay_lastWheel(long clock) {
-    when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true);
     timerWheel.nanos = clock;
-
     long delay = Duration.ofDays(14).toNanos();
     timerWheel.schedule(new Timer(clock + delay));
     assertThat(timerWheel.getExpirationDelay()).isAtMost(delay);
@@ -586,9 +585,10 @@ private void printTimerWheel() {
     System.err.printf(US, "%nCurrent state:%n%s%n%n", builder.deleteCharAt(builder.length() - 1));
   }
 
+  @NullUnmarked
   private static final class Timer extends Node<Long, Long> {
-    Node<Long, Long> prev;
-    Node<Long, Long> next;
+    @Nullable Node<Long, Long> prev;
+    @Nullable Node<Long, Long> next;
     long variableTime;
 
     Timer(long accessTime) {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue193Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue193Test.java
index 0c2a143ade..825a53b85a 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue193Test.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue193Test.java
@@ -15,12 +15,14 @@
 
 import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicLong;
 
+import org.jspecify.annotations.Nullable;
 import org.testng.annotations.Test;
 
 import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
@@ -46,7 +48,7 @@ public final class Issue193Test {
   private final AtomicLong counter = new AtomicLong(0);
   private final FakeTicker ticker = new FakeTicker();
 
-  private ListenableFutureTask<Long> loadingTask;
+  private @Nullable ListenableFutureTask<Long> loadingTask;
 
   private final AsyncCacheLoader<String, Long> loader = (key, executor) -> {
     // Fools the cache into thinking there is a future that's not immediately ready.
@@ -54,7 +56,7 @@ public final class Issue193Test {
     loadingTask = ListenableFutureTask.create(counter::getAndIncrement);
     var f = new CompletableFuture<Long>();
     loadingTask.addListener(() -> {
-      f.complete(Futures.getUnchecked(loadingTask));
+      f.complete(Futures.getUnchecked(requireNonNull(loadingTask)));
     }, executor);
     return f;
   };
@@ -63,6 +65,7 @@ public final class Issue193Test {
   /** This ensures that any outstanding async loading is completed as well */
   private long loadGet(AsyncLoadingCache<String, Long> cache, String key) {
     CompletableFuture<Long> future = cache.get(key);
+    requireNonNull(loadingTask);
     if (!loadingTask.isDone()) {
       loadingTask.run();
     }
@@ -73,7 +76,8 @@ private long loadGet(AsyncLoadingCache<String, Long> cache, String key) {
   public void invalidateDuringRefreshRemovalCheck() {
     var removed = new ArrayList<Long>();
     AsyncLoadingCache<String, Long> cache = Caffeine.newBuilder()
-        .removalListener((String key, Long value, RemovalCause reason) -> removed.add(value))
+        .removalListener(
+            (@Nullable String key, @Nullable Long value, RemovalCause reason) -> removed.add(value))
         .refreshAfterWrite(Duration.ofNanos(10))
         .executor(Runnable::run)
         .ticker(ticker::read)
@@ -87,7 +91,7 @@ public void invalidateDuringRefreshRemovalCheck() {
 
     cache.synchronous().invalidate(testKey); // Invalidate key entirely
     assertThat(cache).doesNotContainKey(testKey); // No value in cache (good)
-    loadingTask.run(); // Completes refresh
+    requireNonNull(loadingTask).run(); // Completes refresh
 
     assertThat(cache).doesNotContainKey(testKey); // Value in cache (bad)
     assertThat(removed).containsExactly(0L, 1L).inOrder(); // 1L was sent to removalListener anyway
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Solr10141Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Solr10141Test.java
index 88279b338b..e3514156ac 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Solr10141Test.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Solr10141Test.java
@@ -66,6 +66,7 @@ public void eviction() {
     var removals = new AtomicLong();
 
     RemovalListener<Long, Val> listener = (k, v, removalCause) -> {
+      assertThat(v).isNotNull();
       assertThat(v.key).isEqualTo(k);
       if (!v.live.compareAndSet(/* expectedValue= */ true, /* newValue= */ false)) {
         throw new RuntimeException(String.format(US,
@@ -143,6 +144,7 @@ public void clear() {
     var failed = new ConcurrentLinkedQueue<Throwable>();
 
     RemovalListener<Long, Val> listener = (k, v, removalCause) -> {
+      assertThat(v).isNotNull();
       assertThat(v.key).isEqualTo(k);
       if (!v.live.compareAndSet(/* expectedValue= */ true, /* newValue= */ false)) {
         throw new RuntimeException(String.format(US,
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/AsyncCacheSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/AsyncCacheSubject.java
index 02d56c2bb0..d2adb0dd9b 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/AsyncCacheSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/AsyncCacheSubject.java
@@ -20,10 +20,13 @@
 import static com.github.benmanes.caffeine.cache.testing.CacheSubject.cache;
 import static com.github.benmanes.caffeine.testing.MapSubject.map;
 import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
 
 import java.util.Map;
 import java.util.concurrent.Future;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.cache.AsyncCache;
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.Subject;
@@ -36,9 +39,9 @@
 public final class AsyncCacheSubject extends Subject {
   private final AsyncCache<?, ?> actual;
 
-  private AsyncCacheSubject(FailureMetadata metadata, AsyncCache<?, ?> subject) {
+  private AsyncCacheSubject(FailureMetadata metadata, @Nullable AsyncCache<?, ?> subject) {
     super(metadata, subject);
-    this.actual = subject;
+    this.actual = requireNonNull(subject);
   }
 
   public static Factory<AsyncCacheSubject, AsyncCache<?, ?>> asyncCache() {
@@ -81,7 +84,7 @@ public void doesNotContainKey(Object key) {
   }
 
   /** Fails if the cache does not contain the given value. */
-  public void containsValue(Object value) {
+  public void containsValue(@Nullable Object value) {
     if (value instanceof Future<?>) {
       check("cache").about(map()).that(actual.asMap()).containsValue(value);
     } else {
@@ -90,7 +93,7 @@ public void containsValue(Object value) {
   }
 
   /** Fails if the cache does not contain the given entry. */
-  public void containsEntry(Object key, Object value) {
+  public void containsEntry(Object key, @Nullable Object value) {
     if (value instanceof Future<?>) {
       check("cache").that(actual.asMap()).containsEntry(key, value);
     } else {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java
index 016c317c0b..26a0d8d8eb 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java
@@ -30,6 +30,7 @@
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Function;
 
+import org.jspecify.annotations.NullUnmarked;
 import org.jspecify.annotations.Nullable;
 
 import com.github.benmanes.caffeine.cache.AsyncCache;
@@ -92,7 +93,6 @@ public final class CacheContext {
   final TrackingExecutor executor;
   final ReferenceType keyStrength;
   final CacheWeigher cacheWeigher;
-  final Expiry<Int, Int> expiry;
   final Map<Int, Int> original;
   final CacheExpiry expiryType;
   final Population population;
@@ -106,6 +106,8 @@ public final class CacheContext {
   final Loader loader;
   final Stats stats;
 
+  final @Nullable Expiry<Int, Int> expiry;
+
   final boolean isAsyncLoader;
 
   CacheBuilder<Object, Object> guava;
@@ -124,7 +126,7 @@ public final class CacheContext {
 
   @Nullable Map<Int, Int> absent;
 
-  @SuppressWarnings({"PMD.ExcessiveParameterList", "TooManyParameters"})
+  @SuppressWarnings({"NullAway.Init", "PMD.ExcessiveParameterList", "TooManyParameters"})
   public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher cacheWeigher,
       Maximum maximumSize, CacheExpiry expiryType, Expire afterAccess, Expire afterWrite,
       Expire refresh, ReferenceType keyStrength, ReferenceType valueStrength,
@@ -159,7 +161,7 @@ public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher c
     this.compute = compute;
     this.expiryType = expiryType;
     this.expiryTime = cacheSpec.expiryTime();
-    this.expiry = expiryType.createExpiry(expiryTime);
+    this.expiry = (expiryType == CacheExpiry.DISABLED) ? null : expiryType.createExpiry(expiryTime);
   }
 
   /** Returns a thread local interner for explicit caching. */
@@ -214,16 +216,19 @@ public Population population() {
 
   public Int firstKey() {
     assertWithMessage("Invalid usage of context").that(firstKey).isNotNull();
+    requireNonNull(firstKey);
     return firstKey;
   }
 
   public Int middleKey() {
     assertWithMessage("Invalid usage of context").that(middleKey).isNotNull();
+    requireNonNull(middleKey);
     return middleKey;
   }
 
   public Int lastKey() {
     assertWithMessage("Invalid usage of context").that(lastKey).isNotNull();
+    requireNonNull(lastKey);
     return lastKey;
   }
 
@@ -417,6 +422,7 @@ public boolean expiresVariably() {
     return (expiryType != CacheExpiry.DISABLED);
   }
 
+  @NullUnmarked
   public Expiry<Int, Int> expiry() {
     return expiry;
   }
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContextSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContextSubject.java
index a61b262db9..4938f9dc0d 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContextSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContextSubject.java
@@ -39,6 +39,8 @@
 import java.util.function.Function;
 import java.util.function.ToLongFunction;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Policy.CacheEntry;
 import com.github.benmanes.caffeine.cache.RemovalCause;
@@ -65,9 +67,9 @@
 public final class CacheContextSubject extends Subject {
   private final CacheContext actual;
 
-  CacheContextSubject(FailureMetadata metadata, CacheContext subject) {
+  CacheContextSubject(FailureMetadata metadata, @Nullable CacheContext subject) {
     super(metadata, subject);
-    this.actual = subject;
+    this.actual = requireNonNull(subject);
   }
 
   public static Factory<CacheContextSubject, CacheContext> context() {
@@ -212,9 +214,9 @@ public static final class StatsSubject extends Subject {
     private final CacheContext actual;
     private final boolean isDirect;
 
-    private StatsSubject(FailureMetadata metadata, CacheContext context) {
+    private StatsSubject(FailureMetadata metadata, @Nullable CacheContext context) {
       super(metadata, context);
-      this.actual = context;
+      this.actual = requireNonNull(context);
       this.isDirect = !context.isRecordingStats()
           || (context.executorType() == CacheExecutor.DIRECT);
     }
@@ -290,6 +292,7 @@ private ListenerSubject(FailureMetadata metadata, CacheContext context,
     private static Factory<ListenerSubject, CacheContext> factoryOf(
         RemovalListenerType... removalListenerTypes) {
       return (metadata, context) -> {
+        requireNonNull(context);
         var subject = Arrays.stream(removalListenerTypes)
             .filter(listener -> listener.isConsumingListener(context))
             .collect(toImmutableMap(identity(), listener -> listener.instance(context)));
@@ -356,7 +359,7 @@ private WithCause(RemovalCause cause) {
       }
 
       @CanIgnoreReturnValue
-      public Exclusive contains(Int key, Int value) {
+      public Exclusive contains(@Nullable Int key, @Nullable Int value) {
         return contains(new SimpleEntry<>(key, value));
       }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheProvider.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheProvider.java
index ba281323ef..aa7d45cfef 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheProvider.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheProvider.java
@@ -100,7 +100,7 @@ private Object[] asTestCases(CacheContext context) {
         }
         params[i] = context.cache().asMap();
       } else if (clazz.isAssignableFrom(Policy.Eviction.class)) {
-        params[i] = context.cache().policy().eviction().orElse(null);
+        params[i] = context.cache().policy().eviction().orElseThrow();
       } else if (clazz.isAssignableFrom(Policy.VarExpiration.class)) {
         params[i] = context.cache().policy().expireVariably().orElseThrow();
       } else if (clazz.isAssignableFrom(Policy.FixedRefresh.class)) {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java
index 69f6cfa86c..d1d5c7e96d 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java
@@ -54,6 +54,7 @@
 import com.github.benmanes.caffeine.cache.Scheduler;
 import com.github.benmanes.caffeine.cache.Weigher;
 import com.github.benmanes.caffeine.cache.testing.RemovalListeners.ConsumingRemovalListener;
+import com.github.benmanes.caffeine.cache.testing.Weighers.SkippedWeigher;
 import com.github.benmanes.caffeine.testing.ConcurrentTestHarness;
 import com.github.benmanes.caffeine.testing.Int;
 import com.google.common.collect.Maps;
@@ -198,12 +199,18 @@ enum CacheWeigher {
     VALUE(() -> (key, value) -> Math.abs(((Int) value).intValue())),
     /** A flag indicating that the entry is weighted by the value's collection size. */
     COLLECTION(() -> (key, value) -> ((Collection<?>) value).size()),
-    /** A flag indicating that the entry's weight is randomly changing. */
+    /**
+     * A flag indicating that the entry's weight is randomly changing and is skipped by automatic
+     * validation checks.
+     */
     RANDOM(Weighers::random),
-    /** A flag indicating that the entry's weight records interactions. */
+    /**
+     * A flag indicating that the entry's weight records interactions and is skipped by automatic
+     * automatic validation checks.
+     */
     @SuppressWarnings("unchecked")
     MOCKITO(() -> {
-      Weigher<Object, Object> weigher = Mockito.mock();
+      SkippedWeigher<Object, Object> weigher = Mockito.mock();
       when(weigher.weigh(any(), any())).thenReturn(1);
       return weigher;
     });
@@ -267,7 +274,7 @@ CacheExpiry[] expiry() default {
   enum CacheExpiry {
     DISABLED {
       @Override public <K, V> Expiry<K, V> createExpiry(Expire expiryTime) {
-        return null;
+        throw new AssertionError();
       }
     },
     MOCKITO {
@@ -419,6 +426,7 @@ enum Loader implements CacheLoader<Int, Int> {
     },
     /** A loader that always returns null (no mapping). */
     NULL {
+      @SuppressWarnings("NullAway")
       @Override public Int load(Int key) {
         return null;
       }
@@ -461,7 +469,9 @@ enum Loader implements CacheLoader<Int, Int> {
       @Override public Int load(Int key) {
         throw new UnsupportedOperationException();
       }
-      @SuppressWarnings({"PMD.ReturnEmptyCollectionRatherThanNull", "ReturnsNullCollection"})
+
+      @SuppressWarnings({"NullAway",
+        "PMD.ReturnEmptyCollectionRatherThanNull", "ReturnsNullCollection"})
       @Override public Map<Int, Int> loadAll(Set<? extends Int> keys) {
         return null;
       }
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSubject.java
index 726e299940..c37db5ff51 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSubject.java
@@ -21,10 +21,13 @@
 import static com.github.benmanes.caffeine.testing.MapSubject.map;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
 
 import java.util.Map;
 import java.util.Objects;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.cache.Cache;
 import com.google.common.testing.GcFinalization;
 import com.google.common.truth.Correspondence;
@@ -44,9 +47,9 @@ public final class CacheSubject extends Subject {
 
   private final Cache<?, ?> actual;
 
-  CacheSubject(FailureMetadata metadata, Cache<?, ?> subject) {
+  CacheSubject(FailureMetadata metadata, @Nullable Cache<?, ?> subject) {
     super(metadata, subject);
-    this.actual = subject;
+    this.actual = requireNonNull(subject);
   }
 
   public static Factory<CacheSubject, Cache<?, ?>> cache() {
@@ -104,7 +107,7 @@ public void doesNotContainKey(Object key) {
   }
 
   /** Fails if the cache does not contain the given value. */
-  public void containsValue(Object value) {
+  public void containsValue(@Nullable Object value) {
     check("cache").about(map()).that(actual.asMap()).containsValue(value);
   }
 
@@ -114,14 +117,16 @@ public void doesNotContainValue(Object value) {
   }
 
   /** Fails if the cache does not contain the given entry. */
-  public void containsEntry(Object key, Object value) {
+  public void containsEntry(Object key, @Nullable Object value) {
+    requireNonNull(value);
     check("cache").that(actual.asMap())
         .comparingValuesUsing(EQUALITY)
         .containsEntry(key, value);
   }
 
   /** Fails if the cache contains the given entry. */
-  public void doesNotContainEntry(Object key, Object value) {
+  public void doesNotContainEntry(Object key, @Nullable Object value) {
+    requireNonNull(value);
     check("cache").that(actual.asMap())
         .comparingValuesUsing(EQUALITY)
         .doesNotContainEntry(key, value);
@@ -162,8 +167,8 @@ public static final class CleanUpSubject extends Subject {
 
     private final Cache<?, ?> actual;
 
-    private CleanUpSubject(FailureMetadata metadata, Cache<?, ?> cache) {
-      super(metadata, cache.asMap());
+    private CleanUpSubject(FailureMetadata metadata, @Nullable Cache<?, ?> cache) {
+      super(metadata, requireNonNull(cache).asMap());
       this.actual = cache;
     }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java
index 712338ba5e..1a6af68282 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java
@@ -34,6 +34,8 @@
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.LoadingCache;
 import com.github.benmanes.caffeine.cache.Policy;
@@ -138,7 +140,8 @@ public static <K, V> Cache<K, V> newGuavaCache(CacheContext context) {
     return castedCache;
   }
 
-  static class GuavaCache<K, V> implements Cache<K, V>, Serializable {
+  @SuppressWarnings("NullAway")
+  static class GuavaCache<K, V> implements Cache<K, @Nullable V>, Serializable {
     private static final long serialVersionUID = 1L;
 
     private final com.google.common.cache.Cache<K, V> cache;
@@ -146,10 +149,10 @@ static class GuavaCache<K, V> implements Cache<K, V>, Serializable {
     private final boolean canSnapshot;
     private final Ticker ticker;
 
-    transient ConcurrentMap<K, V> mapView;
     transient StatsCounter statsCounter;
-    transient Policy<K, V> policy;
-    transient Set<K> keySet;
+    transient @Nullable ConcurrentMap<K, V> mapView;
+    transient @Nullable Policy<K, V> policy;
+    transient @Nullable Set<K> keySet;
 
     GuavaCache(com.google.common.cache.Cache<K, V> cache, CacheContext context) {
       this.canSnapshot = context.expires() || context.refreshes();
@@ -160,12 +163,12 @@ static class GuavaCache<K, V> implements Cache<K, V>, Serializable {
     }
 
     @Override
-    public V getIfPresent(Object key) {
+    public @Nullable V getIfPresent(Object key) {
       return cache.getIfPresent(key);
     }
 
     @Override
-    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
+    public @Nullable V get(K key, Function<? super K, ? extends V> mappingFunction) {
       requireNonNull(mappingFunction);
       try {
         return cache.get(key, () -> {
@@ -201,7 +204,7 @@ public Map<K, V> getAll(Iterable<? extends K> keys, Function<? super Set<? exten
       keys.forEach(Objects::requireNonNull);
       requireNonNull(mappingFunction);
 
-      Map<K, V> found = getAllPresent(keys);
+      Map<K, @Nullable V> found = getAllPresent(keys);
       Set<K> keysToLoad = Sets.difference(ImmutableSet.copyOf(keys), found.keySet());
       if (keysToLoad.isEmpty()) {
         return found;
@@ -494,6 +497,7 @@ static class GuavaLoadingCache<K, V> extends GuavaCache<K, V> implements Loading
     }
 
     @Override
+    @SuppressWarnings("NullAway")
     public V get(K key) {
       try {
         return cache.get(key);
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java
index 18a517e025..4f98c8b5a2 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java
@@ -22,6 +22,8 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.RejectedExecutionException;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
 
@@ -44,7 +46,7 @@ public static <K, V> RemovalListener<K, V> rejecting() {
     return new RejectingRemovalListener<>();
   }
 
-  private static void validate(Object key, Object value, RemovalCause cause) {
+  private static void validate(@Nullable Object key, @Nullable Object value, RemovalCause cause) {
     if (cause != RemovalCause.COLLECTED) {
       requireNonNull(key);
       requireNonNull(value);
@@ -60,7 +62,7 @@ public static final class RejectingRemovalListener<K, V>
     public int rejected;
 
     @Override
-    public void onRemoval(K key, V value, RemovalCause cause) {
+    public void onRemoval(@Nullable K key, @Nullable V value, RemovalCause cause) {
       validate(key, value, cause);
 
       if (reject) {
@@ -83,7 +85,7 @@ public ConsumingRemovalListener() {
     }
 
     @Override
-    public void onRemoval(K key, V value, RemovalCause cause) {
+    public void onRemoval(@Nullable K key, @Nullable V value, RemovalCause cause) {
       validate(key, value, cause);
       removed.add(new RemovalNotification<>(key, value, cause));
     }
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Weighers.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Weighers.java
index d1897c0354..b690593398 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Weighers.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Weighers.java
@@ -42,6 +42,9 @@ public static <K, V> Weigher<K, V> random() {
     return (Weigher<K, V>) RandomWeigher.INSTANCE;
   }
 
+  /** A marker to instruct the validation to not check the entry's weight for data consistency. */
+  public interface SkippedWeigher<K, V> extends Weigher<K, V> {}
+
   static final class ConstantWeigher<K, V> implements Weigher<K, V>, Serializable {
     private static final long serialVersionUID = 1L;
 
@@ -57,7 +60,7 @@ static final class ConstantWeigher<K, V> implements Weigher<K, V>, Serializable
     }
   }
 
-  enum RandomWeigher implements Weigher<Object, Object> {
+  enum RandomWeigher implements SkippedWeigher<Object, Object> {
     INSTANCE;
 
     @Override public int weigh(Object key, Object value) {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java
index e9addaf1a2..780306989a 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java
@@ -25,6 +25,7 @@
 import org.eclipse.collections.impl.list.Interval;
 import org.eclipse.collections.impl.parallel.ParallelIterate;
 import org.eclipse.collections.impl.test.Verify;
+import org.jspecify.annotations.NullUnmarked;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -35,6 +36,7 @@
  *
  * Ported from Eclipse Collections 11.0.
  */
+@NullUnmarked
 @SuppressWarnings("CanIgnoreReturnValueSuggester")
 public abstract class ConcurrentHashMapAcceptanceTest {
   private ExecutorService executor;
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/mutable/ConcurrentHashMapTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/mutable/ConcurrentHashMapTest.java
index c53e059d3f..fc01297a1b 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/mutable/ConcurrentHashMapTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/mutable/ConcurrentHashMapTest.java
@@ -35,6 +35,7 @@
 import org.eclipse.collections.impl.set.mutable.UnifiedSet;
 import org.eclipse.collections.impl.test.Verify;
 import org.eclipse.collections.impl.tuple.ImmutableEntry;
+import org.jspecify.annotations.NullUnmarked;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -43,6 +44,7 @@
  *
  * Ported from Eclipse Collections 11.0.
  */
+@NullUnmarked
 @SuppressWarnings({"all", "CanIgnoreReturnValueSuggester", "IdentityConversion", "unchecked"})
 public abstract class ConcurrentHashMapTest extends ConcurrentHashMapTestCase {
   public static final MutableMap<Integer, MutableBag<Integer>> SMALL_BAG_MUTABLE_MAP =
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/Collection8Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/Collection8Test.java
index 9dbab2ff63..30547dff2a 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/Collection8Test.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/Collection8Test.java
@@ -45,12 +45,15 @@
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import junit.framework.Test;
 
 /**
  * Contains tests applicable to all jdk8+ Collection implementations.
  * An extension of CollectionTest.
  */
+@NullUnmarked
 @SuppressWarnings({"CatchAndPrintStackTrace", "CollectionAddAllToCollectionBlock",
     "CollectionForEach", "CollectionIsEmpty", "CollectionToArray", "CollectorMutability",
     "EmptyCatch", "LabelledBreakTarget", "MemberName", "MethodReferenceUsage", "MissingDefault",
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/ConcurrentHashMap8Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/ConcurrentHashMap8Test.java
index 84bc3b750e..84ba6ede33 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/ConcurrentHashMap8Test.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/ConcurrentHashMap8Test.java
@@ -24,12 +24,15 @@
 import java.util.concurrent.atomic.LongAdder;
 import java.util.function.BiFunction;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
+@NullUnmarked
 @SuppressWarnings({"EmptyCatch", "IdentityConversion", "PreferredInterfaceType",
   "rawtypes", "try", "unchecked", "UnnecessaryFinal"})
 public class ConcurrentHashMap8Test extends JSR166TestCase {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/JSR166TestCase.java b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/JSR166TestCase.java
index 905872dfaa..a6928cc4a0 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/JSR166TestCase.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/JSR166TestCase.java
@@ -99,6 +99,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Pattern;
 
+import org.jspecify.annotations.NullUnmarked;
 import org.jspecify.annotations.Nullable;
 
 import junit.framework.Test;
@@ -193,6 +194,7 @@
  *
  * </ul>
  */
+@NullUnmarked
 @SuppressWarnings({"AnnotateFormatMethod", "ClassEscapesDefinedScope", "CollectionToArray",
     "ConstantField", "EmptyCatch", "EqualsIncompatibleType", "FunctionalInterfaceClash",
     "InterruptedExceptionSwallowed", "JavaUtilDate", "JUnit3FloatingPointComparisonWithoutDelta",
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/MapTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/MapTest.java
index e049ab2d39..7b6b06d7d1 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/MapTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/jsr166/MapTest.java
@@ -16,11 +16,14 @@
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import junit.framework.Test;
 
 /**
  * Contains tests applicable to all Map implementations.
  */
+@NullUnmarked
 @SuppressWarnings({"rawtypes", "unchecked", "UnnecessaryFinal", "UnnecessaryParentheses"})
 public class MapTest extends JSR166TestCase {
     final MapImplementation impl;
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/lincheck/AbstractLincheckCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/lincheck/AbstractLincheckCacheTest.java
index ef523ab511..798dcd5b9f 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/lincheck/AbstractLincheckCacheTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/lincheck/AbstractLincheckCacheTest.java
@@ -25,6 +25,7 @@
 import org.jetbrains.kotlinx.lincheck.paramgen.IntGen;
 import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions;
 import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions;
+import org.jspecify.annotations.Nullable;
 import org.testng.annotations.Test;
 
 import com.github.benmanes.caffeine.cache.Caffeine;
@@ -76,7 +77,7 @@ public void stressTest() {
   /* --------------- Cache --------------- */
 
   @Operation
-  public Integer getIfPresent(@Param(name = "key") int key) {
+  public @Nullable Integer getIfPresent(@Param(name = "key") int key) {
     return cache.getIfPresent(key);
   }
 
@@ -110,7 +111,7 @@ public boolean containsKey(@Param(name = "key") int key) {
   }
 
   @Operation
-  public Integer get_asMap(@Param(name = "key") int key) {
+  public @Nullable Integer get_asMap(@Param(name = "key") int key) {
     return cache.asMap().get(key);
   }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java
index 8dc625538e..41e3047533 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
 
 import java.util.Collection;
 import java.util.Deque;
@@ -25,6 +26,8 @@
 import java.util.Queue;
 import java.util.Set;
 
+import org.jspecify.annotations.Nullable;
+
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.IterableSubject;
 
@@ -34,9 +37,9 @@
  * @author ben.manes@gmail.com (Ben Manes)
  */
 public class CollectionSubject extends IterableSubject {
-  private final Collection<?> actual;
+  private final @Nullable Collection<?> actual;
 
-  public CollectionSubject(FailureMetadata metadata, Collection<?> subject) {
+  public CollectionSubject(FailureMetadata metadata, @Nullable Collection<?> subject) {
     super(metadata, subject);
     this.actual = subject;
   }
@@ -56,6 +59,7 @@ public final void hasSize(long expectedSize) {
 
   /** Fails if the collection does not have less than the given size. */
   public void hasSizeLessThan(long other) {
+    requireNonNull(actual);
     checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other);
     check("size()").that(actual.size()).isLessThan(Math.toIntExact(other));
   }
@@ -83,11 +87,13 @@ public void isExhaustivelyEmpty() {
   }
 
   private void checkIterable() {
+    requireNonNull(actual);
     check("iterator().hasNext()").that(actual.iterator().hasNext()).isFalse();
   }
 
   @SuppressWarnings("CollectionToArray")
   private void checkCollection() {
+    requireNonNull(actual);
     check("size()").that(actual).hasSize(0);
     check("isEmpty()").that(actual).isEmpty();
     check("toArray()").that(actual.toArray()).isEmpty();
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java
index d9272f94ed..0c88752773 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java
@@ -16,11 +16,14 @@
 package com.github.benmanes.caffeine.testing;
 
 import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
 
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 
+import org.jspecify.annotations.Nullable;
+
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.Subject;
 import com.google.common.truth.ThrowableSubject;
@@ -32,9 +35,9 @@
  * @author ben.manes@gmail.com (Ben Manes)
  */
 public final class FutureSubject extends Subject {
-  private final CompletableFuture<?> actual;
+  private final @Nullable CompletableFuture<?> actual;
 
-  private FutureSubject(FailureMetadata metadata, CompletableFuture<?> subject) {
+  private FutureSubject(FailureMetadata metadata, @Nullable CompletableFuture<?> subject) {
     super(metadata, subject);
     this.actual = subject;
   }
@@ -43,12 +46,13 @@ public static Factory<FutureSubject, CompletableFuture<?>> future() {
     return FutureSubject::new;
   }
 
-  public static FutureSubject assertThat(CompletableFuture<?> actual) {
+  public static FutureSubject assertThat(@Nullable CompletableFuture<?> actual) {
     return assertAbout(future()).that(actual);
   }
 
   /** Fails if the future is not done. */
   public void isDone() {
+    requireNonNull(actual);
     if (!actual.isDone()) {
       failWithActual("expected to be done", actual);
     }
@@ -56,6 +60,7 @@ public void isDone() {
 
   /** Fails if the future is done. */
   public void isNotDone() {
+    requireNonNull(actual);
     if (actual.isDone()) {
       failWithActual("expected to not be done", actual);
     }
@@ -63,6 +68,7 @@ public void isNotDone() {
 
   /** Fails if the future is has not completed exceptionally. */
   public void hasCompletedExceptionally() {
+    requireNonNull(actual);
     if (!actual.isCompletedExceptionally()) {
       failWithActual("expected to be completed exceptionally", actual.join());
     }
@@ -70,6 +76,7 @@ public void hasCompletedExceptionally() {
 
   /** Fails if the future is not successful with the given value. */
   public void succeedsWith(int value) {
+    requireNonNull(actual);
     var result = actual.join();
     if (result instanceof Int) {
       check("future").that(result).isEqualTo(Int.valueOf(value));
@@ -79,18 +86,21 @@ public void succeedsWith(int value) {
   }
 
   /** Fails if the future is not successful with the given value. */
-  public void succeedsWith(Object value) {
+  public void succeedsWith(@Nullable Object value) {
+    requireNonNull(actual);
     check("future").that(actual.join()).isEqualTo(value);
   }
 
   /** Fails if the future is not successful with a null value. */
   public void succeedsWithNull() {
+    requireNonNull(actual);
     check("future").that(actual.join()).isNull();
   }
 
   /** Fails if the future is did not fail with the given join() exception. */
   @CanIgnoreReturnValue
   public ThrowableSubject failsWith(Class<? extends RuntimeException> clazz) {
+    requireNonNull(actual);
     try {
       failWithActual("join", actual.join());
       throw new AssertionError();
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java
index 89da30ee3d..92ecba261e 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java
@@ -16,6 +16,7 @@
 package com.github.benmanes.caffeine.testing;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -26,6 +27,8 @@
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 
+import org.jspecify.annotations.Nullable;
+
 import com.google.errorprone.annotations.Immutable;
 
 /**
@@ -48,8 +51,8 @@ public final class Int implements Serializable {
   private final int value;
 
   /** Constructs a newly allocated {@code Int} object with the same {@code value}. */
-  public Int(Int value) {
-    this(value.value);
+  public Int(@Nullable Int value) {
+    this(requireNonNull(value).value);
   }
 
   /**
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java
index 9d034d545e..d53e9a0f67 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertAbout;
 
+import org.jspecify.annotations.Nullable;
+
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.Subject;
 
@@ -27,7 +29,7 @@
  */
 public final class IntSubject extends Subject {
 
-  private IntSubject(FailureMetadata metadata, Int subject) {
+  private IntSubject(FailureMetadata metadata, @Nullable Int subject) {
     super(metadata, subject);
   }
 
@@ -35,7 +37,7 @@ public static Factory<IntSubject, Int> integer() {
     return IntSubject::new;
   }
 
-  public static IntSubject assertThat(Int actual) {
+  public static IntSubject assertThat(@Nullable Int actual) {
     return assertAbout(integer()).that(actual);
   }
 
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java
index 4b793c3624..cd1b19bcc3 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java
@@ -25,6 +25,7 @@
 import java.util.Objects;
 import java.util.function.Predicate;
 
+import org.jspecify.annotations.Nullable;
 import org.slf4j.event.Level;
 
 import com.github.valfirst.slf4jtest.LoggingEvent;
@@ -43,7 +44,8 @@ public final class LoggingEvents extends ForwardingList<LoggingEvent> {
   private final List<Predicate<LoggingEvent>> predicates;
   private final ImmutableList<LoggingEvent> events;
 
-  private ImmutableList<LoggingEvent> filteredEvents;
+  private @Nullable ImmutableList<LoggingEvent> filteredEvents;
+
   private boolean exclusive;
 
   private LoggingEvents(Iterable<LoggingEvent> events) {
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java
index 32c767e4da..eae84aced1 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java
@@ -18,9 +18,12 @@
 import static com.github.benmanes.caffeine.testing.CollectionSubject.collection;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
 
 import java.util.Map;
 
+import org.jspecify.annotations.Nullable;
+
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Range;
 import com.google.common.truth.FailureMetadata;
@@ -34,18 +37,19 @@
  */
 public class MapSubject extends com.google.common.truth.MapSubject {
   @SuppressWarnings("ImmutableMemberCollection")
-  private final Map<?, ?> actual;
+  private final @Nullable Map<?, ?> actual;
 
-  public MapSubject(FailureMetadata metadata, Map<?, ?> subject) {
+  public MapSubject(FailureMetadata metadata, @Nullable Map<?, ?> subject) {
     super(metadata, subject);
     this.actual = subject;
   }
 
+  @SuppressWarnings("NullAway")
   public static Factory<MapSubject, Map<?, ?>> map() {
     return MapSubject::new;
   }
 
-  public static <K, V> MapSubject assertThat(Map<K, V> actual) {
+  public static <K, V> MapSubject assertThat(@Nullable Map<K, V> actual) {
     return assertAbout(map()).that(actual);
   }
 
@@ -56,29 +60,34 @@ public final void hasSize(long expectedSize) {
 
   /** Fails if the map does not have less than the given size. */
   public void hasSizeLessThan(long other) {
+    requireNonNull(actual);
     checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other);
     check("size()").that(actual.size()).isLessThan(Math.toIntExact(other));
   }
 
   /** Fails if the map's size is not in {@code range}. */
   public void hasSizeIn(Range<Integer> range) {
+    requireNonNull(actual);
     check("size()").that(actual.size()).isIn(range);
   }
 
   /** Fails if the map does not contain the given keys, where duplicate keys are ignored. */
   @CanIgnoreReturnValue
   public Ordered containsExactlyKeys(Iterable<?> keys) {
+    requireNonNull(actual);
     return check("containsKeys").that(actual.keySet())
         .containsExactlyElementsIn(ImmutableSet.copyOf(keys));
   }
 
   /** Fails if the map does not contain the given value. */
-  public void containsValue(Object value) {
+  public void containsValue(@Nullable Object value) {
+    requireNonNull(actual);
     check("containsValue").that(actual.values()).contains(value);
   }
 
   /** Fails if the map does contain the given value. */
   public void doesNotContainValue(Object value) {
+    requireNonNull(actual);
     check("containsValue").that(actual.values()).doesNotContain(value);
   }
 
@@ -87,6 +96,7 @@ public void doesNotContainValue(Object value) {
    * methods.
    */
   public void isExhaustivelyEmpty() {
+    requireNonNull(actual);
     isEqualTo(Map.of());
     hasSize(0);
     isEmpty();
diff --git a/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml b/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml
index 8ccad706c3..85ee0daab6 100644
--- a/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml
+++ b/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
 [versions]
 caffeine = "3.1.8"
 junit = "5.11.3"
-reactor = "3.7.0"
+reactor = "3.7.1"
 truth = "1.4.4"
 versions = "0.51.0"
 
diff --git a/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties b/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties
index 0cdabcd31b..c085078342 100644
--- a/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/coalescing-bulkloader-reactor/settings.gradle.kts b/examples/coalescing-bulkloader-reactor/settings.gradle.kts
index 149babf6c3..53023a7268 100644
--- a/examples/coalescing-bulkloader-reactor/settings.gradle.kts
+++ b/examples/coalescing-bulkloader-reactor/settings.gradle.kts
@@ -1,5 +1,5 @@
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
   id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
diff --git a/examples/graal-native/gradle/wrapper/gradle-wrapper.properties b/examples/graal-native/gradle/wrapper/gradle-wrapper.properties
index 0cdabcd31b..c085078342 100644
--- a/examples/graal-native/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/graal-native/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/graal-native/settings.gradle.kts b/examples/graal-native/settings.gradle.kts
index a5e4cd278c..1bb26f570f 100644
--- a/examples/graal-native/settings.gradle.kts
+++ b/examples/graal-native/settings.gradle.kts
@@ -5,7 +5,7 @@ pluginManagement {
   }
 }
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
   id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
diff --git a/examples/hibernate/gradle/wrapper/gradle-wrapper.properties b/examples/hibernate/gradle/wrapper/gradle-wrapper.properties
index 0cdabcd31b..c085078342 100644
--- a/examples/hibernate/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/hibernate/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/hibernate/settings.gradle.kts b/examples/hibernate/settings.gradle.kts
index 8a0dbec0f9..a6fd48777d 100644
--- a/examples/hibernate/settings.gradle.kts
+++ b/examples/hibernate/settings.gradle.kts
@@ -1,5 +1,5 @@
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
   id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
diff --git a/examples/indexable/gradle/wrapper/gradle-wrapper.properties b/examples/indexable/gradle/wrapper/gradle-wrapper.properties
index 0cdabcd31b..c085078342 100644
--- a/examples/indexable/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/indexable/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/indexable/settings.gradle.kts b/examples/indexable/settings.gradle.kts
index e17d97cef9..58d363260e 100644
--- a/examples/indexable/settings.gradle.kts
+++ b/examples/indexable/settings.gradle.kts
@@ -1,5 +1,5 @@
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
   id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
diff --git a/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties b/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties
index 0cdabcd31b..c085078342 100644
--- a/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/resilience-failsafe/settings.gradle.kts b/examples/resilience-failsafe/settings.gradle.kts
index 0ddb809517..090e9af4ed 100644
--- a/examples/resilience-failsafe/settings.gradle.kts
+++ b/examples/resilience-failsafe/settings.gradle.kts
@@ -1,5 +1,5 @@
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
   id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
diff --git a/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties b/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties
index 0cdabcd31b..c085078342 100644
--- a/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/write-behind-rxjava/settings.gradle.kts b/examples/write-behind-rxjava/settings.gradle.kts
index d04c4ad195..c288943379 100644
--- a/examples/write-behind-rxjava/settings.gradle.kts
+++ b/examples/write-behind-rxjava/settings.gradle.kts
@@ -1,5 +1,5 @@
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
   id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fa913b441d..22aeddf9aa 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,14 +7,14 @@ bnd = "7.1.0"
 bouncycastle-jdk18on = "1.79"
 cache2k = "2.6.1.Final"
 caffeine = "3.1.8"
-checkstyle = "10.20.2"
+checkstyle = "10.21.0"
 coherence = "24.09"
 commons-collections4 = "4.4"
 commons-compress = "1.27.1"
 commons-io = "2.18.0"
 commons-lang3 = "3.17.0"
 commons-math3 = "3.6.1"
-commons-text = "1.12.0"
+commons-text = "1.13.0"
 concurrentlinkedhashmap = "1.4.2"
 config = "1.4.3"
 coveralls = "2.12.2"
@@ -32,7 +32,7 @@ felix-scr = "2.2.12"
 findsecbugs = "1.13.0"
 flip-tables = "1.1.1"
 forbidden-apis = "3.8"
-google-java-format = "1.25.0"
+google-java-format = "1.25.2"
 guava = "33.3.1-jre"
 guice = "6.0.0"
 h2 = "2.3.232"
@@ -67,8 +67,9 @@ jvm-dependency-conflict-resolution = "2.1.2"
 kotlin = "2.1.0"
 lincheck = "2.34"
 mockito = "5.14.2"
+mockito-testng = "0.5.2"
 nexus-publish = "2.0.0"
-nullaway = "0.12.1"
+nullaway = "0.12.2"
 nullaway-plugin = "2.1.0"
 okhttp-bom = "4.12.0"
 okio-bom = "3.9.1"
@@ -85,7 +86,7 @@ slf4j-test = "3.0.1"
 snakeyaml = "2.3"
 sonarqube = "6.0.1.5171"
 spotbugs = "4.8.6"
-spotbugs-contrib = "7.6.8"
+spotbugs-contrib = "7.6.9"
 spotbugs-plugin = "6.0.26"
 stream = "2.9.8"
 tcache = "2.0.1"
@@ -171,7 +172,9 @@ junit5-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.re
 kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
 lincheck = { module = "org.jetbrains.kotlinx:lincheck-jvm", version.ref = "lincheck" }
 mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
+mockito-testng = { module = "org.mockito:mockito-testng", version.ref = "mockito-testng" }
 nullaway = { module = "com.uber.nullaway:nullaway", version.ref = "nullaway" }
+nullaway-annotations = { module = "com.uber.nullaway:nullaway-annotations", version.ref = "nullaway" }
 okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp-bom" }
 okio-bom = { module = "com.squareup.okio:okio-bom", version.ref = "okio-bom" }
 osgi-annotations = { module = "org.osgi:org.osgi.service.component.annotations", version.ref = "osgi-annotations" }
@@ -211,6 +214,7 @@ constraints = ["bcel", "bouncycastle-jdk18on", "commons-compress", "commons-text
 errorprone-support = ["errorprone-support", "errorprone-support-refaster"]
 junit = ["junit4", "junit5"]
 junit-engines = ["junit5-vintage", "junit5-testng"]
+mockito = ["mockito", "mockito-testng"]
 osgi-test-compile = ["pax-exam-junit4"]
 osgi-test-runtime = ["felix-framework", "felix-scr", "osgi-function",
   "osgi-promise", "pax-exam-container-native", "pax-exam-link-mvn", "pax-url-aether"]
diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts
index 613c7e28ec..c8e64b1f28 100644
--- a/gradle/plugins/settings.gradle.kts
+++ b/gradle/plugins/settings.gradle.kts
@@ -1,5 +1,5 @@
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
   id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
diff --git a/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts b/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts
index eeb0e7902a..440f9c230e 100644
--- a/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts
+++ b/gradle/plugins/src/main/kotlin/lifecycle/java-library.caffeine.gradle.kts
@@ -41,7 +41,7 @@ tasks.withType<JavaCompile>().configureEach {
     javaModuleVersion = provider { version as String }
     compilerArgs.addAll(listOf("-Xlint:all", "-Xlint:-auxiliaryclass", "-Xlint:-classfile",
       "-Xlint:-exports", "-Xlint:-processing", "-Xlint:-removal", "-Xlint:-requires-automatic",
-      "-parameters"))
+      "-parameters", "-Xmaxerrs", "500", "-Xmaxwarns", "500"))
     if (isCI()) {
       compilerArgs.add("-Werror")
     }
diff --git a/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts b/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts
index 56f34e130a..d50e839965 100644
--- a/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts
+++ b/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts
@@ -1,9 +1,12 @@
 import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
 import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
 import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED
+import net.ltgt.gradle.errorprone.errorprone
+import net.ltgt.gradle.nullaway.nullaway
 
 plugins {
   `java-library`
+  id("errorprone.caffeine")
 }
 
 val mockitoAgent: Configuration by configurations.creating
@@ -13,11 +16,12 @@ dependencies {
   testImplementation(libs.guice)
   testImplementation(libs.truth)
   testImplementation(libs.testng)
-  testImplementation(libs.mockito)
   testImplementation(libs.hamcrest)
   testImplementation(libs.awaitility)
   testImplementation(libs.bundles.junit)
   testImplementation(libs.guava.testlib)
+  testImplementation(libs.bundles.mockito)
+  testImplementation(libs.nullaway.annotations)
   testImplementation(libs.bundles.osgi.test.compile)
 
   testImplementation(platform(libs.asm.bom))
@@ -52,3 +56,18 @@ tasks.withType<Test>().configureEach {
     showCauses = true
   }
 }
+
+tasks.named<JavaCompile>("compileTestJava").configure {
+  options.errorprone.nullaway {
+    customInitializerAnnotations.addAll(listOf(
+      "org.testng.annotations.BeforeClass",
+      "org.testng.annotations.BeforeMethod"))
+    externalInitAnnotations.addAll(listOf(
+      "org.mockito.testng.MockitoSettings",
+      "picocli.CommandLine.Command"))
+    excludedFieldAnnotations.addAll(listOf(
+      "jakarta.inject.Inject",
+      "org.mockito.Captor",
+      "org.mockito.Mock"))
+  }
+}
diff --git a/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts b/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts
index 115c71bedc..6be2731475 100644
--- a/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts
+++ b/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts
@@ -61,12 +61,8 @@ tasks.withType<JavaCompile>().configureEach {
         append(".*")
       })
       disabledChecks().forEach { disable(it) }
-      errorChecks().forEach { error(it) }
 
       nullaway {
-        if (name.contains("Test")) {
-          disable()
-        }
         annotatedPackages.add("com.github.benmanes.caffeine")
         annotatedPackages.add("com.google.common")
         handleTestAssertionLibraries = true
@@ -74,14 +70,12 @@ tasks.withType<JavaCompile>().configureEach {
         suggestSuppressions = true
         checkContracts = true
         isJSpecifyMode = true
+        error()
       }
     }
   }
 }
 
-fun errorChecks() = listOf(
-  "NullAway",
-)
 fun disabledChecks() = listOf(
   "AvoidObjectArrays",
   "AndroidJdkLibsChecker",
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0cdabcd31b..c085078342 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/CaffeinatedGuavaTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/CaffeinatedGuavaTest.java
index ad5af82de7..f43d044be0 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/CaffeinatedGuavaTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/CaffeinatedGuavaTest.java
@@ -105,6 +105,7 @@ void reload_throwable() {
   }
 
   @Test
+  @SuppressWarnings("NullAway")
   void cacheLoader_null() {
     assertThrows(NullPointerException.class, () -> CaffeinatedGuava.caffeinate(null));
 
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/GuavaMapTests.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/GuavaMapTests.java
index c4c550d06a..196322729a 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/GuavaMapTests.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/GuavaMapTests.java
@@ -45,6 +45,7 @@ private static void addGuavaViewTests(TestSuite suite) {
       return cache.asMap();
     })));
     suite.addTest(MapTestFactory.suite("GuavaLoadingView", synchronousGenerator(() -> {
+      @SuppressWarnings("NullAway")
       Cache<String, String> cache = CaffeinatedGuava.build(
           Caffeine.newBuilder().maximumSize(Long.MAX_VALUE), key -> null);
       return cache.asMap();
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderFactory.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderFactory.java
index 1b0a3a2c49..9a8bc53b8d 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderFactory.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderFactory.java
@@ -14,10 +14,13 @@
 
 package com.github.benmanes.caffeine.guava.compatibility;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
@@ -37,17 +40,18 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 class CacheBuilderFactory {
   // Default values contain only 'null', which means don't call the CacheBuilder method (just give
   // the CacheBuilder default).
-  private Set<Integer> concurrencyLevels = Sets.newHashSet((Integer) null);
-  private Set<Integer> initialCapacities = Sets.newHashSet((Integer) null);
-  private Set<Integer> maximumSizes = Sets.newHashSet((Integer) null);
-  private Set<DurationSpec> expireAfterWrites = Sets.newHashSet((DurationSpec) null);
-  private Set<DurationSpec> expireAfterAccesses = Sets.newHashSet((DurationSpec) null);
-  private Set<DurationSpec> refreshes = Sets.newHashSet((DurationSpec) null);
-  private Set<Strength> keyStrengths = Sets.newHashSet((Strength) null);
-  private Set<Strength> valueStrengths = Sets.newHashSet((Strength) null);
+  private Set<Integer> concurrencyLevels = Collections.singleton(null);
+  private Set<Integer> initialCapacities = Collections.singleton(null);
+  private Set<Integer> maximumSizes = Collections.singleton(null);
+  private Set<DurationSpec> expireAfterWrites = Collections.singleton(null);
+  private Set<DurationSpec> expireAfterAccesses = Collections.singleton(null);
+  private Set<DurationSpec> refreshes = Collections.singleton(null);
+  private Set<Strength> keyStrengths = Collections.singleton(null);
+  private Set<Strength> valueStrengths = Collections.singleton(null);
 
   @CanIgnoreReturnValue
   CacheBuilderFactory withConcurrencyLevels(Set<Integer> concurrencyLevels) {
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderGwtTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderGwtTest.java
index 511c992b81..c2f7215dd3 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderGwtTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderGwtTest.java
@@ -25,6 +25,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
@@ -49,6 +51,7 @@
  *
  * @author Jon Donovan
  */
+@NullUnmarked
 @GwtCompatible
 @SuppressWarnings("PreferJavaTimeOverload")
 public class CacheBuilderGwtTest extends TestCase {
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderTest.java
index c2ff09e997..3b81777439 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheBuilderTest.java
@@ -40,6 +40,8 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.RemovalListener;
 import com.github.benmanes.caffeine.cache.Ticker;
@@ -64,6 +66,7 @@
 /**
  * Unit tests for Caffeine.
  */
+@NullUnmarked
 @GwtCompatible(emulated = true)
 @SuppressWarnings({"CacheLoaderNull", "CanonicalDuration",
   "PreferJavaTimeOverload", "ThreadPriorityCheck"})
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheExpirationTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheExpirationTest.java
index 641e291da7..7d2d7ad7c2 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheExpirationTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheExpirationTest.java
@@ -25,6 +25,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
@@ -46,6 +48,7 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 @SuppressWarnings("PreferJavaTimeOverload")
 public class CacheExpirationTest extends TestCase {
 
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheLoadingTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheLoadingTest.java
index a54848a257..959f726311 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheLoadingTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheLoadingTest.java
@@ -39,6 +39,8 @@
 import java.util.concurrent.atomic.AtomicReferenceArray;
 import java.util.logging.Logger;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.github.benmanes.caffeine.guava.compatibility.TestingCacheLoaders.IdentityLoader;
@@ -69,6 +71,7 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 @SuppressWarnings({"CacheLoaderNull", "PreferJavaTimeOverload", "ThreadPriorityCheck"})
 public class CacheLoadingTest extends TestCase {
   static final Logger logger = Logger.getLogger(
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheReferencesTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheReferencesTest.java
index 3c9577afe5..853fc01590 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheReferencesTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/CacheReferencesTest.java
@@ -20,6 +20,8 @@
 
 import java.lang.ref.WeakReference;
 
+import org.jspecify.annotations.Nullable;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.github.benmanes.caffeine.guava.compatibility.CacheBuilderFactory.Strength;
@@ -127,7 +129,7 @@ public void testInvalidate() {
   // a strong reference to that value.
   static final class Key {
     private final int value;
-    private WeakReference<String> toString;
+    private @Nullable WeakReference<String> toString;
 
     Key(int value) {
       this.value = value;
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/EmptyCachesTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/EmptyCachesTest.java
index 0e729d09d9..cfefd324e4 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/EmptyCachesTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/EmptyCachesTest.java
@@ -26,6 +26,8 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.github.benmanes.caffeine.guava.compatibility.CacheBuilderFactory.DurationSpec;
@@ -45,6 +47,7 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 public class EmptyCachesTest extends TestCase {
 
   public void testEmpty() {
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalCacheMapComputeTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalCacheMapComputeTest.java
index 38120cee77..c6247c86ec 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalCacheMapComputeTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalCacheMapComputeTest.java
@@ -22,6 +22,8 @@
 import java.util.function.IntConsumer;
 import java.util.stream.IntStream;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.google.common.cache.Cache;
@@ -32,6 +34,7 @@
 /**
  * Test Java8 map.compute in concurrent cache context.
  */
+@NullUnmarked
 @SuppressWarnings("PreferJavaTimeOverload")
 public class LocalCacheMapComputeTest extends TestCase {
   final int count = 10000;
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalLoadingCacheTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalLoadingCacheTest.java
index f2025663f9..4e4a8e5a18 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalLoadingCacheTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/LocalLoadingCacheTest.java
@@ -25,6 +25,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.google.common.cache.CacheLoader;
@@ -41,6 +43,7 @@
 /**
  * @author Charles Fry
  */
+@NullUnmarked
 @SuppressWarnings("JUnit3FloatingPointComparisonWithoutDelta")
 public class LocalLoadingCacheTest extends TestCase {
   static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0);
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/NullCacheTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/NullCacheTest.java
index 5a683c8c33..50341c3bad 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/NullCacheTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/NullCacheTest.java
@@ -22,6 +22,8 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.junit.Assert.assertThrows;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.github.benmanes.caffeine.guava.compatibility.TestingRemovalListeners.QueuingRemovalListener;
@@ -39,6 +41,7 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 @SuppressWarnings("PreferJavaTimeOverload")
 public class NullCacheTest extends TestCase {
   QueuingRemovalListener<Object, Object> listener;
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/PopulatedCachesTest.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/PopulatedCachesTest.java
index 9fef4d0d7e..04cc386861 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/PopulatedCachesTest.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/PopulatedCachesTest.java
@@ -28,6 +28,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.github.benmanes.caffeine.guava.compatibility.CacheBuilderFactory.DurationSpec;
@@ -50,6 +52,7 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 @SuppressWarnings("CollectionToArray")
 public class PopulatedCachesTest extends TestCase {
   // we use integers as keys; make sure the range covers some values that ARE cached by
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingCacheLoaders.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingCacheLoaders.java
index a68bb3307d..fa41044fea 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingCacheLoaders.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingCacheLoaders.java
@@ -20,6 +20,7 @@
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.jspecify.annotations.NullUnmarked;
 import org.jspecify.annotations.Nullable;
 
 import com.google.common.annotations.GwtCompatible;
@@ -35,6 +36,7 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 @GwtCompatible(emulated = true)
 public class TestingCacheLoaders {
 
diff --git a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingRemovalListeners.java b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingRemovalListeners.java
index a8b3cbda8e..d4ffa8c583 100644
--- a/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingRemovalListeners.java
+++ b/guava/src/test/java/com/github/benmanes/caffeine/guava/compatibility/TestingRemovalListeners.java
@@ -17,6 +17,8 @@
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.jspecify.annotations.NullUnmarked;
+
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
 import com.google.common.annotations.GwtCompatible;
@@ -28,6 +30,7 @@
  *
  * @author mike nonemacher
  */
+@NullUnmarked
 @GwtCompatible(emulated = true)
 class TestingRemovalListeners {
 
diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/CaffeineConfiguration.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/CaffeineConfiguration.java
index 39aee128ee..d17c8b4807 100644
--- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/CaffeineConfiguration.java
+++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/CaffeineConfiguration.java
@@ -184,7 +184,7 @@ public CaffeineConfiguration<K, V> removeCacheEntryListenerConfiguration(
   @CanIgnoreReturnValue
   @SuppressWarnings("PMD.LinguisticNaming")
   public CaffeineConfiguration<K, V> setCacheLoaderFactory(
-      Factory<? extends CacheLoader<K, V>> factory) {
+      @Nullable Factory<? extends CacheLoader<K, V>> factory) {
     checkIfReadOnly();
     delegate.setCacheLoaderFactory(factory);
     return this;
@@ -215,7 +215,7 @@ public boolean hasCacheWriter() {
   @CanIgnoreReturnValue
   @SuppressWarnings("PMD.LinguisticNaming")
   public CaffeineConfiguration<K, V> setCacheWriterFactory(
-      Factory<? extends CacheWriter<? super K, ? super V>> factory) {
+      @Nullable Factory<? extends CacheWriter<? super K, ? super V>> factory) {
     checkIfReadOnly();
     delegate.setCacheWriterFactory(factory);
     return this;
@@ -230,7 +230,7 @@ public Factory<ExpiryPolicy> getExpiryPolicyFactory() {
   @CanIgnoreReturnValue
   @SuppressWarnings("PMD.LinguisticNaming")
   public CaffeineConfiguration<K, V> setExpiryPolicyFactory(
-      Factory<? extends ExpiryPolicy> factory) {
+      @Nullable Factory<? extends ExpiryPolicy> factory) {
     checkIfReadOnly();
     delegate.setExpiryPolicyFactory(factory);
     return this;
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/AbstractJCacheTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/AbstractJCacheTest.java
index 59be4f1c5c..6d5a3ec8ba 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/AbstractJCacheTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/AbstractJCacheTest.java
@@ -16,7 +16,6 @@
 package com.github.benmanes.caffeine.jcache;
 
 import java.time.Duration;
-import java.util.Map;
 import java.util.concurrent.ThreadLocalRandom;
 
 import javax.cache.CacheManager;
@@ -38,6 +37,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.testing.FakeTicker;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.uber.nullaway.annotations.Initializer;
 
 /**
  * A testing harness for simplifying the unit tests.
@@ -97,6 +97,7 @@ public void afterClass() {
   }
 
   /** The base configuration used by the test. */
+  @Initializer
   protected abstract CaffeineConfiguration<Integer, Integer> getConfiguration();
 
   /* --------------- Utility methods ------------- */
@@ -134,8 +135,8 @@ protected CacheLoader<Integer, Integer> getCacheLoader() {
       @Override public Integer load(Integer key) {
         return key;
       }
-      @Override public Map<Integer, Integer> loadAll(Iterable<? extends Integer> keys) {
-        return Maps.asMap(ImmutableSet.copyOf(keys), this::load);
+      @Override public ImmutableMap<Integer, Integer> loadAll(Iterable<? extends Integer> keys) {
+        return Maps.toMap(ImmutableSet.copyOf(keys), this::load);
       }
     };
   }
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/CacheProxyTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/CacheProxyTest.java
index 4fb48b4bcd..15680c6142 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/CacheProxyTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/CacheProxyTest.java
@@ -76,7 +76,7 @@ protected CaffeineConfiguration<Integer, Integer> getLoadingConfiguration() {
   }
 
   @Test
-  @SuppressWarnings({"ObjectToString", "unchecked"})
+  @SuppressWarnings({"NullAway", "ObjectToString", "unchecked"})
   public void getConfiguration_immutable() {
     var config = jcache.getConfiguration(CaffeineConfiguration.class);
     var type = UnsupportedOperationException.class;
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TestCacheLoader.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TestCacheLoader.java
index 613fe9d947..83441bb024 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TestCacheLoader.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TestCacheLoader.java
@@ -19,9 +19,12 @@
 
 import javax.cache.integration.CacheLoader;
 
+import org.jspecify.annotations.NullUnmarked;
+
 /**
  * @author ben.manes@gmail.com (Ben Manes)
  */
+@NullUnmarked
 public final class TestCacheLoader implements CacheLoader<Integer, Integer> {
   @Override public Integer load(Integer key) {
     return null;
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java
index dceeda997a..b4f533827f 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java
@@ -17,6 +17,7 @@
 
 import static com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator.configSource;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static org.junit.Assert.assertThrows;
 
 import java.net.URI;
@@ -274,8 +275,9 @@ static void checkTestCache(CaffeineConfiguration<?, ?> config) {
     assertThat(config.getValueType()).isEqualTo(Object.class);
     assertThat(config.getExecutorFactory().create()).isInstanceOf(TestExecutor.class);
     assertThat(config.getSchedulerFactory().create()).isInstanceOf(TestScheduler.class);
-    assertThat(config.getCacheLoaderFactory().create()).isInstanceOf(TestCacheLoader.class);
     assertThat(config.getCacheWriter()).isInstanceOf(TestCacheWriter.class);
+    assertThat(requireNonNull(config.getCacheLoaderFactory()).create())
+        .isInstanceOf(TestCacheLoader.class);
     assertThat(config.isNativeStatisticsEnabled()).isTrue();
     assertThat(config.isStatisticsEnabled()).isTrue();
     assertThat(config.isManagementEnabled()).isTrue();
@@ -292,7 +294,8 @@ static void checkStoreByValue(CaffeineConfiguration<?, ?> config) {
   }
 
   static void checkListener(CompleteConfiguration<?, ?> config) {
-    var listener = Iterables.getOnlyElement(config.getCacheEntryListenerConfigurations());
+    var listener = requireNonNull(Iterables.getOnlyElement(
+        config.getCacheEntryListenerConfigurations()));
     assertThat(listener.getCacheEntryListenerFactory().create())
         .isInstanceOf(TestCacheEntryListener.class);
     assertThat(listener.getCacheEntryEventFilterFactory().create())
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopierTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopierTest.java
index 9af31ba263..c8a940e925 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopierTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopierTest.java
@@ -54,11 +54,13 @@ public void constructor_null(Set<Class<?>> immutableClasses,
         new JavaSerializationCopier(immutableClasses, deepCopyStrategies));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "copier")
   public void null_object(Copier copier) {
     assertThrows(NullPointerException.class, () -> copy(copier, null));
   }
 
+  @SuppressWarnings("NullAway")
   @Test(dataProvider = "copier")
   public void null_classLoader(Copier copier) {
     assertThrows(NullPointerException.class, () -> copier.copy(1, null));
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java
index d277676bd0..aab392fbc4 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java
@@ -16,6 +16,7 @@
 package com.github.benmanes.caffeine.jcache.event;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static javax.cache.event.EventType.CREATED;
 import static javax.cache.event.EventType.EXPIRED;
 import static javax.cache.event.EventType.REMOVED;
@@ -24,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.quality.Strictness.STRICT_STUBS;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -45,9 +47,10 @@
 import javax.cache.event.CacheEntryUpdatedListener;
 
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.testng.MockitoSettings;
+import org.mockito.testng.MockitoTestNGListener;
 import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.Iterables;
@@ -57,6 +60,9 @@
 /**
  * @author ben.manes@gmail.com (Ben Manes)
  */
+@Test(singleThreaded = true)
+@Listeners(MockitoTestNGListener.class)
+@MockitoSettings(strictness = STRICT_STUBS)
 public final class EventDispatcherTest {
   @Mock Cache<Integer, Integer> cache;
   @Mock CacheEntryCreatedListener<Integer, Integer> createdListener;
@@ -69,11 +75,6 @@ public final class EventDispatcherTest {
   CacheEntryEventFilter<Integer, Integer> allowFilter = event -> true;
   CacheEntryEventFilter<Integer, Integer> rejectFilter = event -> false;
 
-  @BeforeMethod
-  public void beforeMethod() throws Exception {
-    MockitoAnnotations.openMocks(this).close();
-  }
-
   @AfterTest
   public void afterTest() {
     executorService.shutdownNow();
@@ -245,7 +246,7 @@ public void parallel_keys() {
       task.run();
     });
     CacheEntryCreatedListener<Integer, Integer> listener = events -> {
-      var event = Iterables.getOnlyElement(events);
+      var event = requireNonNull(Iterables.getOnlyElement(events));
       if (event.getKey().equals(1)) {
         received1.set(true);
         await().untilTrue(run1);
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java
index 55174b595f..bf200bab5d 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.STRICT_STUBS;
 
 import java.io.IOException;
 
@@ -32,23 +33,22 @@
 
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.testng.annotations.BeforeMethod;
+import org.mockito.testng.MockitoSettings;
+import org.mockito.testng.MockitoTestNGListener;
 import org.testng.annotations.DataProvider;
+import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
 
 /**
  * @author ben.manes@gmail.com (Ben Manes)
  */
+@Test(singleThreaded = true)
 @SuppressWarnings("unchecked")
+@Listeners(MockitoTestNGListener.class)
+@MockitoSettings(strictness = STRICT_STUBS)
 public final class EventTypeAwareListenerTest {
   @Mock Cache<Integer, Integer> cache;
 
-  @BeforeMethod
-  public void before() throws Exception {
-    MockitoAnnotations.openMocks(this).close();
-  }
-
   @Test
   public void closed() throws IOException {
     when(cache.isClosed()).thenReturn(true);
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java
index 1cc67e0504..4a6b6e0a8a 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java
@@ -19,7 +19,7 @@
 
 import javax.cache.event.CacheEntryCreatedListener;
 
-import org.testng.annotations.BeforeTest;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 /**
@@ -28,7 +28,7 @@
 public final class EventTypeFilterTest {
   EventTypeFilter<Integer, Integer> filter;
 
-  @BeforeTest
+  @BeforeClass
   public void before() {
     CacheEntryCreatedListener<Integer, Integer> created = events -> {};
     filter = new EventTypeFilter<>(created, event -> true);
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java
index 3112441b48..2063ee9697 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertThrows;
+import static org.mockito.quality.Strictness.STRICT_STUBS;
 
 import java.util.Iterator;
 import java.util.Map;
@@ -26,8 +27,10 @@
 import javax.cache.event.EventType;
 
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.testng.annotations.BeforeTest;
+import org.mockito.testng.MockitoSettings;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.testing.IteratorFeature;
@@ -36,14 +39,16 @@
 /**
  * @author ben.manes@gmail.com (Ben Manes)
  */
+@Test(singleThreaded = true)
+@Listeners(MockitoTestNGListener.class)
+@MockitoSettings(strictness = STRICT_STUBS)
 public final class JCacheEntryEventTest {
   @Mock Cache<Integer, Integer> cache;
 
   JCacheEntryEvent<Integer, Integer> event;
 
-  @BeforeTest
-  public void before() throws Exception {
-    MockitoAnnotations.openMocks(this).close();
+  @BeforeMethod
+  public void before() {
     event = new JCacheEntryEvent<>(cache, EventType.CREATED,
         1, /* hasOldValue= */ true, 2, 3);
   }
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java
index 174ddf9c75..44db10fc59 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.quality.Strictness.STRICT_STUBS;
 
 import java.util.Arrays;
 import java.util.Iterator;
@@ -29,9 +30,11 @@
 import javax.cache.event.CacheEntryRemovedListener;
 
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.testng.MockitoSettings;
+import org.mockito.testng.MockitoTestNGListener;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
+import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
 
 import com.github.benmanes.caffeine.cache.RemovalCause;
@@ -41,6 +44,9 @@
 /**
  * @author ben.manes@gmail.com (Ben Manes)
  */
+@Test(singleThreaded = true)
+@Listeners(MockitoTestNGListener.class)
+@MockitoSettings(strictness = STRICT_STUBS)
 public final class JCacheEvictionListenerTest {
   JCacheEvictionListener<Integer, Integer> listener;
   JCacheStatisticsMXBean statistics;
@@ -49,8 +55,7 @@ public final class JCacheEvictionListenerTest {
   @Mock Cache<Integer, Integer> cache;
 
   @BeforeMethod
-  public void before() throws Exception {
-    MockitoAnnotations.openMocks(this).close();
+  public void before() {
     statistics = new JCacheStatisticsMXBean();
     var dispatcher = new EventDispatcher<Integer, Integer>(Runnable::run);
     listener = new JCacheEvictionListener<>(dispatcher, statistics);
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheAccessExpiryTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheAccessExpiryTest.java
index 25b3ebbd14..7d987b0842 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheAccessExpiryTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheAccessExpiryTest.java
@@ -99,6 +99,7 @@ public void get_absent() {
   @Test(dataProvider = "eternal")
   public void get_present(boolean eternal) {
     var expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     if (eternal) {
       expirable.setExpireTimeMillis(Long.MAX_VALUE);
     }
@@ -127,6 +128,7 @@ public void get_loading_absent() {
     assertThat(jcacheLoading.get(KEY_1)).isEqualTo(KEY_1);
 
     var expirable = getExpirable(jcacheLoading, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -134,11 +136,13 @@ public void get_loading_absent() {
   @Test(dataProvider = "eternal")
   public void get_loading_present(boolean eternal) {
     var expirable = getExpirable(jcacheLoading, KEY_1);
+    assertThat(expirable).isNotNull();
     if (eternal) {
       expirable.setExpireTimeMillis(Long.MAX_VALUE);
     }
 
     assertThat(jcacheLoading.get(KEY_1)).isEqualTo(VALUE_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -161,6 +165,7 @@ public void getAll_absent() {
   public void getAll_present(boolean eternal) {
     for (Integer key : keys) {
       var expirable = getExpirable(jcacheLoading, key);
+      assertThat(expirable).isNotNull();
       if (eternal) {
         expirable.setExpireTimeMillis(Long.MAX_VALUE);
       }
@@ -170,6 +175,7 @@ public void getAll_present(boolean eternal) {
 
     for (Integer key : keys) {
       var expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
@@ -191,6 +197,7 @@ public void invoke_absent() {
   @Test(dataProvider = "eternal")
   public void invoke_present(boolean eternal) {
     var expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     if (eternal) {
       expirable.setExpireTimeMillis(Long.MAX_VALUE);
     }
@@ -223,6 +230,7 @@ public void invokeAll_present() {
 
     for (Integer key : keys) {
       var expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
@@ -235,6 +243,7 @@ public void removeConditionally() {
     assertThat(jcache.remove(KEY_1, VALUE_2)).isFalse();
 
     var expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -246,6 +255,7 @@ public void replaceConditionally() {
     assertThat(jcache.replace(KEY_1, VALUE_2, VALUE_3)).isFalse();
 
     var expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCombinedExpiryTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCombinedExpiryTest.java
index b773fdcdfa..01ede2ea13 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCombinedExpiryTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCombinedExpiryTest.java
@@ -100,6 +100,7 @@ public void put_expired() {
 
     jcache.put(KEY_1, VALUE_2);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -113,6 +114,7 @@ public void putIfAbsent_expired() {
 
     assertThat(jcache.putIfAbsent(KEY_1, VALUE_2)).isTrue();
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.get()).isEqualTo(VALUE_2);
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCreationExpiryTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCreationExpiryTest.java
index 5980bd839d..01dee9d3be 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCreationExpiryTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheCreationExpiryTest.java
@@ -88,6 +88,7 @@ public void get_expired() {
   public void get_loading_absent() {
     assertThat(jcacheLoading.get(KEY_1)).isEqualTo(KEY_1);
     Expirable<Integer> expirable = getExpirable(jcacheLoading, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -99,6 +100,7 @@ public void get_loading_expired() {
 
     assertThat(jcacheLoading.get(KEY_1)).isEqualTo(KEY_1);
     Expirable<Integer> expirable = getExpirable(jcacheLoading, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -120,7 +122,9 @@ public void get_loading_present() {
 
     assertThat(jcacheLoading.get(KEY_1)).isEqualTo(VALUE_1);
     Expirable<Integer> expirable = getExpirable(jcacheLoading, KEY_1);
-    assertThat(expirable.getExpireTimeMillis()).isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
+    assertThat(expirable).isNotNull();
+    assertThat(expirable.getExpireTimeMillis())
+        .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
   }
 
   /* --------------- getAndPut --------------- */
@@ -130,6 +134,7 @@ public void getAndPut_absent() {
     assertThat(jcache.getAndPut(KEY_1, VALUE_1)).isNull();
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -141,6 +146,7 @@ public void getAndPut_expired() {
 
     assertThat(jcache.getAndPut(KEY_1, VALUE_1)).isNull();
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -152,7 +158,9 @@ public void getAndPut_present() {
 
     assertThat(jcache.getAndPut(KEY_1, VALUE_2)).isEqualTo(VALUE_1);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
-    assertThat(expirable.getExpireTimeMillis()).isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
+    assertThat(expirable).isNotNull();
+    assertThat(expirable.getExpireTimeMillis())
+        .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
   }
 
   /* --------------- put --------------- */
@@ -162,6 +170,7 @@ public void put_absent() {
     jcache.put(KEY_1, VALUE_1);
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -173,6 +182,7 @@ public void put_expired() {
 
     jcache.put(KEY_1, VALUE_2);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -184,7 +194,9 @@ public void put_present() {
 
     jcache.put(KEY_1, VALUE_2);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
-    assertThat(expirable.getExpireTimeMillis()).isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
+    assertThat(expirable).isNotNull();
+    assertThat(expirable.getExpireTimeMillis())
+        .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
   }
 
   /* --------------- putAll --------------- */
@@ -195,6 +207,7 @@ public void putAll_absent() {
 
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
@@ -208,6 +221,7 @@ public void putAll_expired() {
     jcache.putAll(entries);
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
@@ -221,7 +235,9 @@ public void putAll_present() {
     jcache.putAll(entries);
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
-      assertThat(expirable.getExpireTimeMillis()).isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
+      assertThat(expirable).isNotNull();
+      assertThat(expirable.getExpireTimeMillis())
+          .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
     }
   }
 
@@ -232,6 +248,7 @@ public void putIfAbsent_absent() {
     jcache.putIfAbsent(KEY_1, VALUE_1);
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -243,6 +260,7 @@ public void putIfAbsent_expired() {
 
     assertThat(jcache.putIfAbsent(KEY_1, VALUE_2)).isTrue();
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.get()).isEqualTo(VALUE_2);
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
@@ -263,7 +281,9 @@ public void putIfAbsent_present() {
 
     jcache.putIfAbsent(KEY_1, VALUE_2);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
-    assertThat(expirable.getExpireTimeMillis()).isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
+    assertThat(expirable).isNotNull();
+    assertThat(expirable.getExpireTimeMillis())
+        .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
   }
 
   /* --------------- invoke --------------- */
@@ -277,6 +297,7 @@ public void invoke_absent() {
     assertThat(result).isNull();
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -293,6 +314,7 @@ public void invoke_expired() {
     assertThat(result).isNull();
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -309,7 +331,9 @@ public void invoke_present() {
     assertThat(result).isNull();
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
-    assertThat(expirable.getExpireTimeMillis()).isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
+    assertThat(expirable).isNotNull();
+    assertThat(expirable.getExpireTimeMillis())
+        .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
   }
 
   /* --------------- invokeAll --------------- */
@@ -324,6 +348,7 @@ public void invokeAll_absent() {
 
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
@@ -342,6 +367,7 @@ public void invokeAll_expired() {
 
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
@@ -360,6 +386,7 @@ public void invokeAll_present() {
 
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
     }
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheUpdateExpiryTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheUpdateExpiryTest.java
index 0a8808b8d4..ee1cd93f07 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheUpdateExpiryTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/expiry/JCacheUpdateExpiryTest.java
@@ -85,6 +85,7 @@ public void getAndPut() {
     assertThat(value).isEqualTo(VALUE_1);
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -96,6 +97,7 @@ public void getAndReplace() {
 
     assertThat(jcache.getAndReplace(KEY_1, VALUE_2)).isEqualTo(VALUE_1);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -107,6 +109,7 @@ public void put() {
 
     jcache.put(KEY_1, VALUE_2);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis()
             ).isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -119,6 +122,7 @@ public void putAll() {
     jcache.putAll(entries);
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
@@ -131,6 +135,7 @@ public void replace() {
 
     jcache.replace(KEY_1, VALUE_2);
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -142,6 +147,7 @@ public void replaceConditionally() {
 
     assertThat(jcache.replace(KEY_1, VALUE_1, VALUE_2)).isTrue();
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -153,7 +159,9 @@ public void replaceConditionally_failed() {
 
     assertThat(jcache.replace(KEY_1, VALUE_2, VALUE_3)).isFalse();
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
-    assertThat(expirable.getExpireTimeMillis()).isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
+    assertThat(expirable).isNotNull();
+    assertThat(expirable.getExpireTimeMillis())
+        .isEqualTo(START_TIME.plus(EXPIRY_DURATION).toMillis());
   }
 
   @Test
@@ -168,6 +176,7 @@ public void invoke() {
     assertThat(result).isNull();
 
     Expirable<Integer> expirable = getExpirable(jcache, KEY_1);
+    assertThat(expirable).isNotNull();
     assertThat(expirable.getExpireTimeMillis())
         .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
   }
@@ -185,6 +194,7 @@ public void invokeAll() {
 
     for (Integer key : keys) {
       Expirable<Integer> expirable = getExpirable(jcache, key);
+      assertThat(expirable).isNotNull();
       assertThat(expirable.getExpireTimeMillis())
           .isEqualTo(currentTime().plus(EXPIRY_DURATION).toMillis());
     }
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/processor/EntryProcessorTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/processor/EntryProcessorTest.java
index 0acf5843d3..ac9bf03a1f 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/processor/EntryProcessorTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/processor/EntryProcessorTest.java
@@ -33,6 +33,7 @@
 import javax.cache.integration.CacheWriter;
 import javax.cache.processor.MutableEntry;
 
+import org.jspecify.annotations.Nullable;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -104,7 +105,7 @@ public void writeOccursForInitialLoadOfEntry() {
     assertThat(value).isNull();
   }
 
-  private static Object process(MutableEntry<Integer, Integer> entry) {
+  private static @Nullable Object process(MutableEntry<Integer, Integer> entry) {
     var value = 1 + firstNonNull(entry.getValue(), 0);
     entry.setValue(value);
     return null;
@@ -135,7 +136,7 @@ public void deleteAll(Collection<?> keys) {
   }
 
   final class MapLoader implements CacheLoader<Integer, Integer> {
-    @Override public Integer load(Integer key) {
+    @Override public @Nullable Integer load(Integer key) {
       loads++;
       return map.get(key);
     }
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/size/JCacheMaximumWeightTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/size/JCacheMaximumWeightTest.java
index 2497598b91..a2a6a5495c 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/size/JCacheMaximumWeightTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/size/JCacheMaximumWeightTest.java
@@ -16,6 +16,7 @@
 package com.github.benmanes.caffeine.jcache.size;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 
 import java.util.Optional;
 import java.util.OptionalLong;
@@ -46,7 +47,7 @@ public final class JCacheMaximumWeightTest extends AbstractJCacheTest {
   @Override
   protected CaffeineConfiguration<Integer, Integer> getConfiguration() {
     CacheEntryRemovedListener<Integer, Integer> listener = events ->
-        removedWeight.addAndGet(Iterables.getOnlyElement(events).getValue());
+        removedWeight.addAndGet(requireNonNull(Iterables.getOnlyElement(events)).getValue());
     var configuration = new CaffeineConfiguration<Integer, Integer>();
     configuration.setMaximumWeight(OptionalLong.of(MAXIMUM));
     configuration.setWeigherFactory(Optional.of(() -> (key, value) -> value));
diff --git a/settings.gradle.kts b/settings.gradle.kts
index cab1d2d545..451101b3ab 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -2,9 +2,9 @@ pluginManagement {
   includeBuild("gradle/plugins")
 }
 plugins {
-  id("com.gradle.develocity") version "3.18.2"
+  id("com.gradle.develocity") version "3.19"
   id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2"
-  id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
+  id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
 }
 
 dependencyResolutionManagement {