In a non-tx cache, sometimes it's possible for a get(k) to return null even though a previous putIfAbsent(k, v) returned a non-null value and the only concurrent operations on the cache are concurrent putIfAbsent calls.
Say [B, A, C] are the owners of k (C just joined)
1. A starts a putIfAbsent(k, v1) command, sends it to B
2. B forwards the command to A and C
3. C writes k=v1
4. C becomes the primary owner of k (owners are now [C, A])
5. A/B see the new topology before committing and throw an outdatedTopologyException
6. A retries the command, sends it to C
7. C forwards the command to A, which writes k=v1
8. C doesn't have to update the entry, returns null
If, between steps 3 and 7, another thread on A starts a putIfAbsent(k, v2) command, the command will fail and return v1 (because the primary owner already has a value). However, a subsequent get(k) command will return null, because A is an owner and doesn't have the value.