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

ben-manes / caffeine / #5594

06 Jul 2026 06:08PM UTC coverage: 99.713% (-0.2%) from 99.892%
#5594

push

github

ben-manes
Match ConcurrentHashMap for containsAll on the map views

keySet()/values().containsAll inherited AbstractCollection's loop,
which threw NullPointerException on a null element and iterated even
for a self-check. CHM and Guava both skip a null element (false) and
short-circuit containsAll(self) to true; Caffeine's removeAll and
entrySet were already lenient, leaving these two views the holdout.
Override containsAll across the bounded, unbounded, and async views.
The CaffeinatedGuava facade's override (a workaround for the old NPE)
is now redundant, so drop it and delegate to the view.

4106 of 4132 branches covered (99.37%)

42 of 42 new or added lines in 3 files covered. (100.0%)

17 existing lines in 5 files now uncovered.

8350 of 8374 relevant lines covered (99.71%)

1.0 hits per line

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

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

18
import static java.util.Objects.requireNonNull;
19

20
import java.util.Map;
21
import java.util.Set;
22
import java.util.concurrent.CompletableFuture;
23
import java.util.concurrent.CompletionException;
24
import java.util.concurrent.Executor;
25
import java.util.function.Function;
26

27
import org.jspecify.annotations.NonNull;
28
import org.jspecify.annotations.NullMarked;
29
import org.jspecify.annotations.Nullable;
30

31
/**
32
 * Computes or retrieves values, based on a key, for use in populating a {@link LoadingCache} or
33
 * {@link AsyncLoadingCache}.
34
 * <p>
35
 * Most implementations will only need to implement {@link #load}. Other methods may be
36
 * overridden as desired.
37
 * <p>
38
 * Usage example:
39
 * {@snippet class=com.github.benmanes.caffeine.cache.Snippets region=loader_basic lang=java}
40
 *
41
 * @param <K> the type of keys
42
 * @param <V> the type of values. A loader may return null values if and only if it declares a
43
 *     nullable value type. A cache may return null values if and only if its loader does. (Null
44
 *     values are still never <i>stored</i> in the cache.)
45
 * @author ben.manes@gmail.com (Ben Manes)
46
 */
47
@NullMarked
48
@FunctionalInterface
49
@SuppressWarnings({"FunctionalInterfaceMethodChanged",
50
    "JavadocDeclaration", "JavadocReference", "PMD.SignatureDeclareThrowsException"})
51
public interface CacheLoader<K, V extends @Nullable Object> extends AsyncCacheLoader<K, V> {
52

53
  /**
54
   * Computes or retrieves the value corresponding to {@code key}.
55
   * <p>
56
   * <b>Warning:</b> loading <b>must not</b> attempt to update any mappings of this cache directly.
57
   *
58
   * @param key the non-null key whose value should be loaded
59
   * @return the value associated with {@code key} or {@code null} if not found
60
   * @throws Exception or Error, in which case the mapping is unchanged
61
   * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is
62
   *         treated like any other {@code Exception} in all respects except that, when it is
63
   *         caught, the thread's interrupt status is set
64
   */
65
  V load(K key) throws Exception;
66

67
  /**
68
   * Computes or retrieves the values corresponding to {@code keys}. This method is called by
69
   * {@link LoadingCache#getAll}.
70
   * <p>
71
   * If the returned map doesn't contain all requested {@code keys}, then the entries it does
72
   * contain will be cached, and {@code getAll} will return the partial results. If the returned map
73
   * contains extra keys not present in {@code keys} then all returned entries will be cached, but
74
   * only the entries for {@code keys} will be returned from {@code getAll}.
75
   * <p>
76
   * This method should be overridden when bulk retrieval is significantly more efficient than many
77
   * individual lookups. Note that {@link LoadingCache#getAll} will defer to individual calls to
78
   * {@link LoadingCache#get} if this method is not overridden.
79
   * <p>
80
   * <b>Warning:</b> loading <b>must not</b> attempt to update any mappings of this cache directly.
81
   *
82
   * @param keys the unique, non-null keys whose values should be loaded
83
   * @return a map from each key in {@code keys} to the value associated with that key; <b>may not
84
   *         contain null values</b>
85
   * @throws Exception or Error, in which case the mappings are unchanged
86
   * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is
87
   *         treated like any other {@code Exception} in all respects except that, when it is
88
   *         caught, the thread's interrupt status is set
89
   */
90
  default Map<? extends K, ? extends @NonNull V> loadAll(Set<? extends K> keys) throws Exception {
91
    throw new UnsupportedOperationException();
1✔
92
  }
93

94
  /**
95
   * Asynchronously computes or retrieves the value corresponding to {@code key}.
96
   * <p>
97
   * <b>Warning:</b> loading <b>must not</b> attempt to update any mappings of this cache directly.
98
   *
99
   * @param key the non-null key whose value should be loaded
100
   * @param executor the executor that asynchronously loads the entry
101
   * @return the future value associated with {@code key}
102
   */
103
  @Override
104
  default CompletableFuture<? extends V> asyncLoad(K key, Executor executor) throws Exception {
105
    requireNonNull(key);
1✔
106
    requireNonNull(executor);
1✔
107
    return CompletableFuture.supplyAsync(() -> {
1✔
108
      try {
109
        return load(key);
1✔
110
      } catch (RuntimeException e) {
1✔
111
        throw e;
1✔
112
      } catch (InterruptedException e) {
1✔
113
        Thread.currentThread().interrupt();
1✔
114
        throw new CompletionException(e);
1✔
115
      } catch (Exception e) {
1✔
116
        throw new CompletionException(e);
1✔
117
      }
118
    }, executor);
119
  }
120

121
  /**
122
   * Asynchronously computes or retrieves the values corresponding to {@code keys}. This method is
123
   * called by {@link AsyncLoadingCache#getAll}.
124
   * <p>
125
   * If the returned map doesn't contain all requested {@code keys}, then the entries it does
126
   * contain will be cached, and {@code getAll} will return the partial results. If the returned map
127
   * contains extra keys not present in {@code keys} then all returned entries will be cached, but
128
   * only the entries for {@code keys} will be returned from {@code getAll}.
129
   * <p>
130
   * This method should be overridden when bulk retrieval is significantly more efficient than many
131
   * individual lookups. Note that {@link AsyncLoadingCache#getAll} will defer to individual calls
132
   * to {@link AsyncLoadingCache#get} if this method is not overridden.
133
   * <p>
134
   * <b>Warning:</b> loading <b>must not</b> attempt to update any mappings of this cache directly.
135
   *
136
   * @param keys the unique, non-null keys whose values should be loaded
137
   * @param executor the executor that asynchronously loads the entries
138
   * @return a future containing the map from each key in {@code keys} to the value associated with
139
   *         that key; <b>may not contain null values</b>
140
   */
141
  @Override
142
  default CompletableFuture<? extends Map<? extends K, ? extends @NonNull V>> asyncLoadAll(
143
      Set<? extends K> keys, Executor executor) throws Exception {
144
    requireNonNull(keys);
1✔
145
    requireNonNull(executor);
1✔
146
    return CompletableFuture.supplyAsync(() -> {
1✔
147
      try {
148
        return loadAll(keys);
1✔
149
      } catch (RuntimeException e) {
1✔
150
        throw e;
1✔
151
      } catch (InterruptedException e) {
1✔
152
        Thread.currentThread().interrupt();
1✔
153
        throw new CompletionException(e);
1✔
154
      } catch (Exception e) {
1✔
155
        throw new CompletionException(e);
1✔
156
      }
157
    }, executor);
158
  }
159

160
  /**
161
   * Computes or retrieves a replacement value corresponding to an already-cached {@code key}. If
162
   * the replacement value is not found, then the mapping will be removed if {@code null} is
163
   * returned. This method is called when an existing cache entry is refreshed by
164
   * {@link Caffeine#refreshAfterWrite}, or through a call to {@link LoadingCache#refresh}.
165
   * <p>
166
   * <b>Warning:</b> loading <b>must not</b> attempt to update any mappings of this cache directly
167
   * or block waiting for other cache operations to complete.
168
   * <p>
169
   * <b>Note:</b> <i>all exceptions thrown by this method will be logged and then swallowed</i>.
170
   *
171
   * @param key the non-null key whose value should be loaded
172
   * @param oldValue the non-null old value corresponding to {@code key}
173
   * @return the new value associated with {@code key}, or {@code null} if the mapping is to be
174
   *         removed
175
   * @throws Exception or Error, in which case the mapping is unchanged
176
   * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is
177
   *         treated like any other {@code Exception} in all respects except that, when it is
178
   *         caught, the thread's interrupt status is set
179
   */
180
  default V reload(K key, @NonNull V oldValue) throws Exception {
181
    return load(key);
1✔
182
  }
183

184
  /**
185
   * Asynchronously computes or retrieves a replacement value corresponding to an already-cached
186
   * {@code key}. If the replacement value is not found then the mapping will be removed if
187
   * {@code null} is computed. This method is called when an existing cache entry is refreshed by
188
   * {@link Caffeine#refreshAfterWrite}, or through a call to {@link LoadingCache#refresh}.
189
   * <p>
190
   * <b>Warning:</b> loading <b>must not</b> attempt to update any mappings of this cache directly
191
   * or block waiting for other cache operations to complete.
192
   * <p>
193
   * <b>Note:</b> <i>all exceptions thrown by this method will be logged and then swallowed</i>.
194
   *
195
   * @param key the non-null key whose value should be loaded
196
   * @param oldValue the non-null old value corresponding to {@code key}
197
   * @param executor the executor with which the entry is asynchronously loaded
198
   * @return a future containing the new value associated with {@code key}, or containing
199
   *         {@code null} if the mapping is to be removed
200
   */
201
  @Override
202
  default CompletableFuture<? extends V> asyncReload(
203
      K key, @NonNull V oldValue, Executor executor) throws Exception {
204
    requireNonNull(key);
1✔
205
    requireNonNull(executor);
1✔
206
    return CompletableFuture.supplyAsync(() -> {
1✔
207
      try {
208
        return reload(key, oldValue);
1✔
209
      } catch (RuntimeException e) {
1✔
210
        throw e;
1✔
211
      } catch (InterruptedException e) {
1✔
212
        Thread.currentThread().interrupt();
1✔
213
        throw new CompletionException(e);
1✔
UNCOV
214
      } catch (Exception e) {
×
UNCOV
215
        throw new CompletionException(e);
×
216
      }
217
    }, executor);
218
  }
219

220
  /**
221
   * Returns a cache loader that delegates to the supplied mapping function for retrieving the
222
   * values. Note that {@link #load} will silently discard any additional mappings loaded when
223
   * retrieving the {@code key} prior to returning the value to the cache.
224
   * <p>
225
   * Usage example:
226
   * {@snippet class=com.github.benmanes.caffeine.cache.Snippets region=loader_bulk lang=java}
227
   *
228
   * @param <K> the key type
229
   * @param <V> the value type
230
   * @param mappingFunction the function to compute the values
231
   * @return a cache loader that delegates to the supplied {@code mappingFunction}
232
   * @throws NullPointerException if the mappingFunction is null
233
   */
234
  @SuppressWarnings("FunctionalInterfaceClash")
235
  static <K, V extends @Nullable Object> CacheLoader<K, V> bulk(Function<? super Set<? extends K>,
236
      ? extends Map<? extends K, ? extends @NonNull V>> mappingFunction) {
237
    requireNonNull(mappingFunction);
1✔
238
    return new CacheLoader<>() {
1✔
239
      /*
240
       * If the caller passes a mapping function that may ever return partial results, then calls to
241
       * load() may return null. In that case, the caller should type the return value of bulk(...)
242
       * as a CacheLoader<Foo, @Nullable Bar>, rather than a CacheLoader<Foo, Bar>.
243
       */
244
      @SuppressWarnings("NullAway")
245
      @Override public V load(K key) {
246
        return loadAll(Set.of(key)).get(key);
1✔
247
      }
248
      @Override public Map<? extends K, ? extends @NonNull V> loadAll(Set<? extends K> keys) {
249
        return mappingFunction.apply(keys);
1✔
250
      }
251
    };
252
  }
253
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc