putIfAbsent()
Store a value only when the key is free, read the existing value from the result, and know when computeIfAbsent is the better tool.
default V putIfAbsent(K key, V value)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$key | K | The key to store the value under. |
$value | V | The value to store if the key is absent or mapped to null. Evaluated before the call either way. |
Return value
VThe value already associated with the key, or null if there was none (or it was null), in which case the given value was stored.
Errors
UnsupportedOperationException if the map is unmodifiable; NullPointerException if the key or value is null and the map does not permit nulls; ClassCastException or IllegalArgumentException if the key or value cannot be stored.
putIfAbsent(key, value) stores the value only if the key has no mapping, or is mapped to null. It returns the value that was already there, or null when it stored the new one. It was added to Map as a default method in Java 8.
Atomic on ConcurrentHashMap
The default method in Map is a plain get followed by a put. HashMap overrides it with a single lookup, but it takes no lock, so a shared HashMap is no safer than writing get and put yourself. On a ConcurrentHashMap the check and the write are a single atomic step. When two request threads race to claim the same key, exactly one gets null back and wins; the other sees the winner's value. That is how you build idempotency keys for a payment endpoint, de-duplicate webhook deliveries, or register a handler exactly once.
The return value is the old value
The result is what was there before, not what is there now. On the first call for a key it is null, so this line throws NullPointerException the first time it runs:
roles.putIfAbsent(user, new ArrayList<>()).add("admin"); // NPE on first call
roles.computeIfAbsent(user, k -> new ArrayList<>()).add("admin"); // works
computeIfAbsent returns the current value, existing or new, so it is the right tool for "get or create".
The value is created every time
Like any argument, the value is evaluated before the call. putIfAbsent(id, new ArrayList<>()) allocates a list on every call, and putIfAbsent(id, loadUser(id)) loads the user even when it is already cached. Use putIfAbsent when the value is cheap or already built (a token, a timestamp, an ID); use computeIfAbsent when creating it costs something.
Null counts as absent
A key mapped to null is treated as absent and gets overwritten. This differs from containsKey, which reports such a key as present. It is one more reason not to store null values.
Example
When to use it
- Recording idempotency keys so a retried payment request returns the original result instead of charging twice.
- Ignoring duplicate webhook deliveries by claiming each event ID in a ConcurrentHashMap.
- Filling in default settings without overwriting values the user already chose.
Frequently asked questions
What is the difference between putIfAbsent and computeIfAbsent?
putIfAbsent takes a ready-made value, always evaluates it, and returns the previous value (null on insert). computeIfAbsent takes a function that runs only when the key is missing, and returns the current value, so you can chain calls on it.
Is putIfAbsent thread-safe?
Only if the map is. On ConcurrentHashMap it is atomic. On HashMap there is no locking at all, and a HashMap must not be shared between threads that write to it.
What does putIfAbsent return?
The value that was already associated with the key, or null if there was none, in which case your value was stored.
More examples
Idempotency keys on a payment endpoint
A retried request with the same key gets the original result instead of charging twice. On ConcurrentHashMap only one of two simultaneous requests can see null.
Map<String, String> processed = new ConcurrentHashMap<>();
String handle(String idempotencyKey, String paymentId) {
String earlier = processed.putIfAbsent(idempotencyKey, paymentId);
return earlier == null
? "201 created " + paymentId
: "200 replay of " + earlier;
}
void main() {
IO.println(handle("key-abc", "pay-1"));
IO.println(handle("key-abc", "pay-2"));
IO.println(handle("key-def", "pay-3"));
}
// Output:
// 201 created pay-1
// 200 replay of pay-1
// 201 created pay-3The return value is not the stored value
The empty list was stored, but the call returned the previous value, null, so add failed. computeIfAbsent hands back the list that is in the map.
void main() {
Map<String, List<String>> roles = new HashMap<>();
try {
roles.putIfAbsent("ada", new ArrayList<>()).add("admin");
} catch (NullPointerException e) {
IO.println("NullPointerException: putIfAbsent returned null");
}
IO.println(roles);
roles.computeIfAbsent("ada", k -> new ArrayList<>()).add("admin");
IO.println(roles);
}
// Output:
// NullPointerException: putIfAbsent returned null
// {ada=[]}
// {ada=[admin]}A null value counts as absent
containsKey says the key is present, yet putIfAbsent overwrote its null value. The two methods disagree about null, so keep nulls out of maps.
void main() {
Map<String, String> prefs = new HashMap<>();
prefs.put("theme", null);
IO.println("containsKey: " + prefs.containsKey("theme"));
String r = prefs.putIfAbsent("theme", "dark");
IO.println("returned: " + r);
IO.println("theme: " + prefs.get("theme"));
}
// Output:
// containsKey: true
// returned: null
// theme: dark
Discussion