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.

Signature
default V putIfAbsent(K key, V value)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$keyKThe key to store the value under.
$valueVThe 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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-3

The 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.

Example · java
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.

Example · java
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

  • Be the first to comment on this lesson.