Method Signature

public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
  • key — the key to look up
  • mappingFunction — receives the key; its return value is inserted as the new value
  • Returns — the existing value if the key was already present, or the newly computed value
  • Side effect — if the function returns null, no entry is inserted

Basic Example

import java.util.HashMap;
import java.util.Map;

public class ComputeIfAbsentExample {

    public static void main(String[] args) {
        Map<Integer, String> numbers = new HashMap<>();
        numbers.put(1, "one");
        numbers.put(2, "two");
        numbers.put(3, "three");

        System.out.println("Before: " + numbers);

        // Key 4 is absent — value will be computed and inserted
        numbers.computeIfAbsent(4, k -> "four");

        // Key 5 is absent — lambda shorthand
        numbers.computeIfAbsent(5, k -> "five");

        // Key 1 already exists — no change
        numbers.computeIfAbsent(1, k -> "ONE");

        System.out.println("After:  " + numbers);
    }
}

Output:

Before: {1=one, 2=two, 3=three}
After:  {1=one, 2=two, 3=three, 4=four, 5=five}

Key 1 remains "one"computeIfAbsent() never overwrites existing values.


Common Pattern: Map of Lists

computeIfAbsent() is especially useful for building a Map<K, List<V>> — the classic "group by" pattern:

import java.util.*;

public class GroupByExample {

    public static void main(String[] args) {
        List<String> words = List.of("apple", "ant", "banana", "avocado", "blueberry");

        Map<Character, List<String>> grouped = new HashMap<>();

        for (String word : words) {
            char firstLetter = word.charAt(0);
            // Create the list if this letter hasn't been seen yet
            grouped.computeIfAbsent(firstLetter, k -> new ArrayList<>()).add(word);
        }

        grouped.forEach((letter, list) ->
                System.out.println(letter + ": " + list));
    }
}

Output:

a: [apple, ant, avocado]
b: [banana, blueberry]

Without computeIfAbsent(), you'd need to check whether the list exists, create it if not, then add — three lines instead of one.


Before vs. After Java 8

Old approach:

if (!grouped.containsKey(firstLetter)) {
    grouped.put(firstLetter, new ArrayList<>());
}
grouped.get(firstLetter).add(word);

With computeIfAbsent():

grouped.computeIfAbsent(firstLetter, k -> new ArrayList<>()).add(word);

Related Map Compute Methods

MethodBehavior
computeIfAbsent(k, fn)Insert only if key is absent
computeIfPresent(k, fn)Update only if key exists
compute(k, fn)Always compute; handles both cases
getOrDefault(k, default)Read-only; returns default without inserting
putIfAbsent(k, v)Insert a fixed value if absent (no function)

Memoization with computeIfAbsent()

computeIfAbsent() is perfect for caching computed results — only run the expensive computation once per key:

Map<Integer, Long> fibCache = new HashMap<>();

long fibonacci(int n) {
    if (n <= 1) return n;
    return fibCache.computeIfAbsent(n, k -> fibonacci(k - 1) + fibonacci(k - 2));
}

System.out.println(fibonacci(40)); // 102334155

The function is called at most once per key. On subsequent calls with the same key, the cached value is returned immediately. This turns an exponential recursion into a linear one.


Thread-Safe Use with ConcurrentHashMap

computeIfAbsent() is atomic on ConcurrentHashMap — the check and insertion happen as a single operation. This is important in concurrent environments where two threads might try to initialize the same key simultaneously:

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, List<String>> groupedResults = new ConcurrentHashMap<>();

// Safe to call from multiple threads simultaneously
groupedResults.computeIfAbsent("category-A", k -> new ArrayList<>()).add("item1");
groupedResults.computeIfAbsent("category-A", k -> new ArrayList<>()).add("item2");

With a regular HashMap, this pattern is not thread-safe — two threads could both call the mapping function and the second would overwrite the first's result. Always use ConcurrentHashMap for shared maps accessed from multiple threads.


computeIfAbsent() vs putIfAbsent()

Both insert a value only if the key is absent, but there's an important difference:

// putIfAbsent — the new ArrayList is ALWAYS created, even if the key exists
map.putIfAbsent(key, new ArrayList<>());

// computeIfAbsent — the lambda is ONLY called if the key is absent
map.computeIfAbsent(key, k -> new ArrayList<>());
MethodWhen is value computed?Takes a function?
computeIfAbsent(k, fn)Only if key absentYes — receives the key
putIfAbsent(k, v)Always (value evaluated before call)No — fixed value

For values that are cheap to create (like a constant string), putIfAbsent() is fine. For values that are expensive to create (like a new database connection or a populated list), always use computeIfAbsent() to avoid unnecessary work.


Summary

computeIfAbsent() shines when you need lazy initialization of map values — especially for Map<K, List<V>> or Map<K, Set<V>> grouping patterns and for memoization caches. Prefer it over putIfAbsent() when the value is expensive to create. Use it with ConcurrentHashMap for thread-safe lazy initialization.