162

The JavaDoc of ConcurrentHashMap says this:

Like Hashtable but unlike HashMap, this class does not allow null to be used as a key or value.

My question: Why?

2nd question: Why doesn't Hashtable allow null?

I've used a lot of HashMaps for storing data. But when changing to ConcurrentHashMap I got several times into trouble because of NullPointerExceptions.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Marcel
  • 3,749
  • 6
  • 29
  • 35
  • 1
    I think it's an extremely annoying inconsistency. EnumMap doesn't allow null either. There's obviously no technical limitation that disallows null keys. for a Map, simply a V-typed field will provide support for null keys (probably another boolean field if you want to differentiate between null value and no value). – RAY May 13 '11 at 15:41
  • 8
    A better question is "why does HashMap allow a null key and null values?". Or possibly, "why does Java allow null to inhabit all types?", or even "why does Java have nulls at all?". – Jed Wesley-Smith Dec 30 '12 at 06:50

7 Answers7

245

From the author of ConcurrentHashMap himself (Doug Lea):

The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated. The main one is that if map.get(key) returns null, you can't detect whether the key explicitly maps to null vs the key isn't mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.

Bruno
  • 119,590
  • 31
  • 270
  • 376
  • 11
    Thank you, but what about having null as the key? – AmitW Sep 25 '17 at 08:34
  • 3
    why not use `Optional`s as values internally – benez Sep 18 '18 at 14:45
  • 6
    @benez `Optional` is a Java 8 feature, which wasn't available back then (Java 5). You could use `Optional`s now, indeed. – Bruno Sep 18 '18 at 15:02
  • @AmitW, I think the ans is same ie, ambiguities. For example suppose one thread makes a key as null and stores a value against it. Then another thread changed another key to null. When second thread tries to add new value, it will be replaced. If second thread tries to get value, it will get the value for a different key, the one modified by first one. Such situation should be avoided. – Dexter Oct 29 '19 at 17:37
  • 1
    @Dexter isn't it the same for not-null keys? – Bee Mar 20 '22 at 14:59
  • @Bee- I think, I was not clear enough in my previous explanation. Let me try again. If multiple concurrent threads make entry to the concurrent hash map, there will be multiple null keys. Now when we call map.get(null), map will not be able to differentiate which "null" key's value to return. – Dexter Mar 21 '22 at 18:36
  • there will be multiple null keys no, right? It won't allow duplicate keys anyway. In java `null == null` is true. if what you say is true, even hashmaps shouldn't allow null keys. but they do. – Bee Jun 01 '22 at 10:46
54

I believe it is, at least in part, to allow you to combine containsKey and get into a single call. If the map can hold nulls, there is no way to tell if get is returning a null because there was no key for that value, or just because the value was null.

Why is that a problem? Because there is no safe way to do that yourself. Take the following code:

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}

Since m is a concurrent map, key k may be deleted between the containsKey and get calls, causing this snippet to return a null that was never in the table, rather than the desired KeyNotPresentException.

Normally you would solve that by synchronizing, but with a concurrent map that of course won't work. Hence the signature for get had to change, and the only way to do that in a backwards-compatible way was to prevent the user inserting null values in the first place, and continue using that as a placeholder for "key not found".

Alice Purcell
  • 12,622
  • 6
  • 51
  • 57
  • You can do `map.getOrDefault(key, NULL_MARKER)`. If it's `null`, the value was `null`. If it returns `NULL_MARKER`, the value was not present. – Oliv Aug 31 '18 at 10:05
  • @Oliv Only as of Java 8. Also, there may not be a sensible null marker for that type. – Alice Purcell Sep 20 '18 at 20:18
  • @AlicePurcell, "but with a concurrent map that of course won't work" - why, i can synchronize on the concurrent version similarly - so wondering why it won't work. can you elaborate this. – samshers Sep 01 '19 at 13:14
  • 1
    @samshers No operations on a concurrent map synchronize, so you'd need to externally synchronize all calls, at which point you've not only lost all the performance benefit of having a concurrent map, you've left a trap for future maintainers who would naturally expect to be able to safely access a concurrent map without synchronizing. – Alice Purcell Sep 10 '19 at 08:48
  • @AlicePurcell, great. Though technically possible, that's definitely going to be a maintenance nightmare and it's not gonna be expected by any later users that they have to sync on the concurrent version. – samshers Sep 10 '19 at 20:16
5

Josh Bloch designed HashMap; Doug Lea designed ConcurrentHashMap. I hope that isn't libelous. Actually I think the problem is that nulls often require wrapping so that the real null can stand for uninitialized. If client code requires nulls then it can pay the (admittedly small) cost of wrapping nulls itself.

SkyWalker
  • 28,384
  • 14
  • 74
  • 132
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
2

You can't synchronize on a null.

Edit: This isn't exactly why in this case. I initially thought there was something fancy going on with locking things against concurrent updates or otherwise using the Object monitor to detect if something was modified, but upon examining the source code it appears I was wrong - they lock using a "segment" based on a bitmask of the hash.

In that case, I suspect they did it to copy Hashtable, and I suspect Hashtable did it because in the relational database world, null != null, so using a null as a key has no meaning.

Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • 1
    Huh? There is no synchronization done on the keys and values of a Map. That would make no sense. – Tobias Müller Mar 30 '09 at 19:23
  • There is other sorts of locking done. That's what makes it "Concurrent". In order to do that, it needs an Object to hang on. – Paul Tomblin Mar 30 '09 at 19:26
  • 2
    Why isn't there a speciall Object internally that could be used for synchronizing null values? e.g. "private Object NULL = new Object();". I think I've seen this before... – Marcel Mar 30 '09 at 19:41
  • What other sorts of locking do you mean? – Tobias Müller Mar 30 '09 at 19:50
  • Actually, now that I look at the source code http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/ConcurrentHashMap.java I'm having serious doubts about that. It appears it uses segment locking, not locking on individual items. – Paul Tomblin Mar 30 '09 at 20:00
  • It does use segment locking, but at the same time it only makes sense that having to read a null that might be a Number as someone tries to read it out as a string as this person uses said number.toString() to create another mapping to be a no no. Like I said, A light from heaven. – WolfmanDragon Mar 30 '09 at 20:10
  • Paul, please note that the source you linked is not the current official Sun source, but what looks like a draft for JSR-166. The details are essentially the same however. – sooniln Mar 30 '09 at 21:16
1

I guess that the following snippet of the API documentation gives a good hint: "This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details."

They probably just wanted to make ConcurrentHashMap fully compatible/interchangeable to Hashtable. And as Hashtable does not allow null keys and values..

Tobias Müller
  • 371
  • 2
  • 8
  • 2
    And why doesn't Hashtable support null? – Marcel Mar 30 '09 at 19:39
  • From looking at its code, I don't see an obvious reason why Hashtable does not allow null values. Maybe it was just an API decision from back when the class was created?! HashMap has some special handling for the null-case internally which Hashtable does not. (It always throws NullPointerException.) – Tobias Müller Mar 30 '09 at 19:49
-1

ConcurrentHashMap is thread-safe. I believe that not allowing null keys and values was a part of making sure that it is thread-safe.

Kevin Crowell
  • 10,082
  • 4
  • 35
  • 51
-4

I don't think disallowing null value is a correct option. In many cases, we do want do put a key with null value into the con-current map. However, by using ConcurrentHashMap, we cannot do that. I suggest that the coming version of JDK can support that.

yinhaomin
  • 327
  • 2
  • 4