ben-manes/caffeine · error · NullPointerException
null map
Error message
null map
What it means
LocalAsyncCache.resolve joins the future returned by an async bulk mapping function; if it completed with a null map (signaled by the internal NullMapCompletionException), resolve converts it into NullPointerException("null map"). Bulk loads must return a map (an empty one if nothing was loaded) — null is a contract violation, matching the Map/ConcurrentMap rule that null collections are forbidden.
Source
Thrown at caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncCache.java:693
return resolve(asyncCache().get(key, mappingFunction));
}
@Override
public Map<K, V> getAll(
Iterable<? extends K> keys,
Function<
? super Set<? extends K>,
? extends Map<? extends K, ? extends V>> mappingFunction) {
return resolve(asyncCache().getAll(keys, mappingFunction));
}
@SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
"PMD.PreserveStackTrace", "UnusedException"})
protected static <T> T resolve(CompletableFuture<T> future) {
try {
return future.join();
} catch (NullMapCompletionException e) {
throw new NullPointerException("null map");
} catch (CompletionException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else if (e.getCause() instanceof Error) {
throw (Error) e.getCause();
}
throw e;
}
}
@Override
public void put(K key, V value) {
requireNonNull(value);
asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {View on GitHub (pinned to 9da6581ee3)
Solutions
- Return an empty map (or a map covering the requested keys) instead of null from loadAll/asyncLoadAll and from getAll mapping functions
- Wrap third-party calls: return result != null ? result : Map.of();
- Treat 'no data found' as empty, not null — absent keys are simply missing from the result
Example fix
// before CompletableFuture<Map<K, V>> f = CompletableFuture.completedFuture(repo.findAll(keys)); // null when empty cache.getAll(keys, f::join... ); // NullPointerException: null map // after var found = repo.findAll(keys); // may be null return CompletableFuture.completedFuture(found == null ? Map.<K, V>of() : found);
Defensive patterns
Strategy: validation
Validate before calling
// Null-safe the mapping result before it reaches getAll:
static <K, V> CompletableFuture<Map<K, V>> nullSafe(CompletableFuture<Map<K, V>> f) {
return f.thenApply(m -> (m == null) ? Map.<K, V>of() : m);
}
cache.getAll(keys, ks -> nullSafe(bulkLoad(ks))); Try / catch
try {
return cache.synchronous().getAll(keys);
} catch (NullPointerException e) {
if ("null map".equals(e.getMessage())) {
// loader returned null: treat as empty and continue
return Map.of();
}
throw e;
} Prevention
- Always return a map from loadAll/asyncLoadAll — empty when nothing matched, never null
- Wrap third-party bulk APIs: result != null ? result : Map.of()
- Assert non-null bulk results in loader unit tests
When it happens
Trigger: cache.getAll(keys, mappingFunction) where mappingFunction's future completes with null; a CacheLoader/AsyncCacheLoader whose loadAll/asyncLoadAll returns null (e.g. from a repository returning null on empty results).
Common situations: DAO/repository layers that signal "not found" with null instead of an empty map; Optional.map(...) chains that yield null futures; third-party bulk APIs returning null for empty input sets.
Related errors
- throw new ExceptionInInitializerError(e)
- An invalid state was detected, occurring when the key's equa
- Proxy required
- Invalid option
- key %s value was set to %s, must be an integer
AI-assisted analysis of ben-manes/caffeine@9da6581ee3 (2026-08-14).
Data as JSON: /api/errors/4c2787b20d63c5fe.
Report an issue: GitHub.