computeIfAbsent()
Get a value or create it on first use in one call, for grouping into lists and caching, without breaking the map from inside the function.
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$key | K | The key to look up and, if absent, create a value for. |
$mappingFunction | Function<? super K, ? extends V> | Called with the key only when there is no value (or the value is null). Returning null stores nothing. |
Return value
VThe current value for the key: the existing one, or the newly computed one. null if the key was absent and the function returned null.
Errors
NullPointerException if the function is null, or the key is null and the map does not permit null keys; UnsupportedOperationException if the map is unmodifiable. HashMap throws ConcurrentModificationException if the function modifies the map; ConcurrentHashMap may throw IllegalStateException for a recursive update.
computeIfAbsent(key, fn) returns the value for a key. If there is none (or it is null), it calls fn with the key, stores the result and returns it. The function runs only on a miss. This one call replaces the get, null check, create, put sequence. It was added to Map in Java 8.
Grouping into lists
Because it returns the value now in the map, you can chain on it. This is the standard way to build a map of lists, a multimap, without a library:
byCustomer.computeIfAbsent(order.customer(), _ -> new ArrayList<>()).add(order);
The _ is an unnamed lambda parameter (Java 22 and later), used here because the function does not need the key. For streams, Collectors.groupingBy does the same job.
Caching and memoising
cache.computeIfAbsent(id, this::load) loads each key once. If the function returns null, nothing is stored and the call returns null, so a lookup for an unknown ID does not fill the cache with nulls and will try again next time.
Do not touch the map from inside the function
The function must not add or remove entries in the same map. A recursive memoised function that calls computeIfAbsent on the same map from inside its own mapping function is the usual way to hit this. Since Java 9, HashMap detects it and throws ConcurrentModificationException; ConcurrentHashMap may throw IllegalStateException("Recursive update"). Use an explicit get and put for recursive memoisation.
ConcurrentHashMap
On ConcurrentHashMap the whole operation is atomic: the function runs at most once per absent key, even when many request threads ask at the same moment. The price is that the function runs while part of the map is locked, and other threads updating that key, or keys that share its internal bin, wait. Keep the function short. Do not make an HTTP call or a database query inside it; that turns a cache miss into a stall for unrelated keys. For slow loads, store a CompletableFuture as the value or use a dedicated caching library.
Example
When to use it
- Grouping order rows from a database query by customer ID into a map of lists.
- Caching parsed templates or compiled regular expressions per name, created on first use.
- Keeping one rate-limit counter per API key in a ConcurrentHashMap, created on the first request.
Frequently asked questions
How do I create a map of lists in Java?
Use map.computeIfAbsent(key, k -> new ArrayList<>()).add(value). It creates the list the first time a key is seen and returns the existing list after that.
Is computeIfAbsent thread-safe?
On ConcurrentHashMap it is atomic and runs the function at most once per absent key. Keep the function short because other updates to the same part of the map wait for it. On HashMap it is not thread-safe.
Why does computeIfAbsent throw ConcurrentModificationException?
The mapping function changed the same map, usually through a recursive call to computeIfAbsent. HashMap checks for this since Java 9. Compute the value first, or use get and put, instead of modifying the map inside the function.
More examples
A cache that loads each key once
The second request for a1 is served from the map and the loader is not called. Compare getOrDefault, whose default argument would run every time.
int loads = 0;
String loadUser(String id) {
loads++;
return "user-" + id.toUpperCase();
}
void main() {
Map<String, String> cache = new HashMap<>();
IO.println(cache.computeIfAbsent("a1", this::loadUser));
IO.println(cache.computeIfAbsent("a1", this::loadUser));
IO.println(cache.computeIfAbsent("b2", this::loadUser));
IO.println("loads: " + loads);
}
// Output:
// user-A1
// user-A1
// user-B2
// loads: 2Returning null stores nothing
A null result is not cached, so a user created later will be found on the next lookup. If you want to cache misses too, store an Optional or a sentinel value.
Map<String, String> database = Map.of("u1", "[email protected]");
void main() {
Map<String, String> emails = new HashMap<>();
String hit = emails.computeIfAbsent("u1", database::get);
String miss = emails.computeIfAbsent("u9", database::get);
IO.println(hit + " " + miss);
IO.println("u9 cached: " + emails.containsKey("u9"));
IO.println("size: " + emails.size());
}
// Output:
// [email protected] null
// u9 cached: false
// size: 1Recursive updates break the map
The inner call stores a value while the outer computeIfAbsent is still running, and HashMap detects the change. The explicit get and put version does the same memoising safely.
Map<Integer, Long> memo = new HashMap<>(Map.of(0, 0L, 1, 1L));
long fib(int n) {
return memo.computeIfAbsent(n, k -> fib(k - 1) + fib(k - 2));
}
long fibSafe(int n) {
Long known = memo.get(n);
if (known != null) {
return known;
}
long value = fibSafe(n - 1) + fibSafe(n - 2);
memo.put(n, value);
return value;
}
void main() {
try {
fib(5);
} catch (ConcurrentModificationException e) {
IO.println("ConcurrentModificationException");
}
IO.println(fibSafe(50));
}
// Output:
// ConcurrentModificationException
// 12586269025
Discussion