Difference #1
computeIfAbsent takes a mapping function, that is called to obtain the value if the key is missing.
putIfAbsent takes the value directly.
If the value is expensive to obtain, then putIfAbsent wastes that if the key already exists.
A common “expensive” value is e.g. new ArrayList<>() for when you’re creating a Map<K, List<V>>, where creating a new list when the key already exists (which then discards the new list) generates unnecessary garbage.
Difference #2
computeIfAbsent returns “the current (existing or computed) value associated with the specified key, or null if the computed value is null”.
putIfAbsent returns “the previous value associated with the specified key, or null if there was no mapping for the key”.
So, if the key already exists, they return the same thing, but if the key is missing, computeIfAbsent returns the computed value, while putIfAbsent return null.
Difference #3
Both method define “absent” as key missing or existing value is null, but:
computeIfAbsent will not put a null value if the key is absent.
putIfAbsent will put the value if the key is absent, even if the value is null.
It makes no difference for future calls to computeIfAbsent, putIfAbsent, and get calls, but it does make a difference to calls like getOrDefault and containsKey.