Why does Map.of not allow null keys and values?

JavaDictionaryNullHashmapJava 9

Java Problem Overview


With Java 9, new factory methods have been introduced for the List, Set and Map interfaces. These methods allow quickly instantiating a Map object with values in one line. Now, if we consider:

Map<Integer, String> map1 = new HashMap<Integer, String>(Map.of(1, "value1", 2, "value2", 3, "value3"));
map1.put(4, null);

The above is allowed without any exception while if we do:

Map<Integer, String> map2 = Map.of(1, "value1", 2, "value2", 3, "value3", 4, null );

It throws:

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
..

I am not able to get, why null is not allowed in second case.

I know HashMap can take null as a key as well as a value but why was that restricted in the case of Map.of?

The same thing happens in the case of java.util.Set.of("v1", "v2", null) and java.util.List.of("v1", "v2", null).

Java Solutions


Solution 1 - Java

As others pointed out, the Map contract allows rejecting nulls...

> [S]ome implementations prohibit null keys and values [...]. Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException.

... and the collection factories (not just on maps) make use of that.

> They disallow null keys and values. Attempts to create them with null keys or values result in NullPointerException.

But why?

Allowing null in collections is by now seen as a design error. This has a variety of reasons. A good one is usability, where the most prominent trouble maker is Map::get. If it returns null, it is unclear whether the key is missing or the value was null. Generally speaking, collections that are guaranteed null free are easier to use. Implementation-wise, they also require less special casing, making the code easier to maintain and more performant.

You can listen to Stuart Marks explain it in this talk but JEP 269 (the one that introduced the factory methods) summarizes it as well:

> Null elements, keys, and values will be disallowed. (No recently introduced collections have supported nulls.) In addition, prohibiting nulls offers opportunities for a more compact internal representation, faster access, and fewer special cases.

Since HashMap was already out in the wild when this was slowly discovered, it was too late to change it without breaking existing code but most recent implementations of those interfaces (e.g. ConcurrentHashMap) do not allow null anymore and the new collections for the factory methods are no exception.

(I thought another reason was that explicitly using null values was seen as a likely implementation error but I got that wrong. That was about to duplicate keys, which are illegal as well.)

So disallowing null had some technical reason but it was also done to improve the robustness of the code using the created collections.

Solution 2 - Java

Exactly - a HashMap is allowed to store null, not the Map returned by the static factory methods. Not all maps are the same.

Generally as far as I know it has a mistake in the first place to allow nulls in the HashMap as keys, newer collections ban that possibility to start with.

Think of the case when you have an Entry in your HashMap that has a certain key and value == null. You do get, it returns null. What does the mean? It has a mapping of null or it is not present?

Same goes for a Key - hashcode from such a null key has to treated specially all the time. Banning nulls to start with - make this easier.

Solution 3 - Java

In my opinion non-nullability makes sense for keys, but not for values.

  1. The Map interface becomes a weak contract, you can't trust it's behaviour without looking at the implementation, and that is assuming you have access to see it. Java11's Map.of() does not allow null values while HashMap does, but they both implement the same Map contract - how come?
  2. Having a null value or no value at all could not, and arguably should not be considered as a distinct scenario. When you get the value for a key and you get null, who cares if it was explicitly put there or not? there's simply no value for the supplied key, the map does not contain such key, simple as that. Null is null, there's no nullnull or null^2
  3. Existing libraries make extensive use of map as a means to pass properties to them, many of them optional, this makes Map.of() useless as a construct for such structures.
  4. Kotlin enforces nullability at compile time and allows for maps with null values with no known issues.
  5. The reason behind not allowing null values is probably just due to implementation detail and convenience of the creator.

Solution 4 - Java

While HashMap does allow null values, Map.of does not use a HashMap and throws an exception if one is used either as key or value, as documented:

>The Map.of() and Map.ofEntries() static factory methods provide a convenient way to create immutable maps. The Map instances created by these methods have the following characteristics: > >- ... > >- They disallow null keys and values. Attempts to create them with null keys or values result in NullPointerException.

Solution 5 - Java

The major difference is: when you build your own Map the "option 1" way ... then you are implicitly saying: "I want to have full freedom in what I am doing".

So, when you decide that your map should have a null key or value (maybe for the reasons listed here) then you are free to do so.

But "option 2" is about a convenience thing - probably intended to be used for constants. And the people behind Java simply decided: "when you use these convenience methods, then the resulting map shall be null-free".

Allowing for null values means that

 if (map.contains(key)) 

is not the same as

 if (map.get(key) != null)

which might be a problem sometimes. Or more precisely: it is something to remember when dealing with that very map object.

And just an anecdotal hint why this seems to be a reasonable approach: our team implemented similar convenience methods ourselves. And guess what: without knowing anything about plans about future Java standard methods - our methods do the exact same thing: they return an immutable copy of the incoming data; and they throw up when you provide null elements. We are even so strict that when you pass in empty lists/maps/... we complain as well.

Solution 6 - Java

Allowing nulls in maps has been an error. We can see it now, but I guess it wasn't clear when HashMap was introduced. NullpointerException is the most common bug seen in production code.

I would say that the JDK goes in the direction of helping developers fight the NPE plague. Some examples:

  • Introduction of Optional
  • Collectors.toMap(keyMapper, valueMapper) doesn't allow neither the keyMapper nor the valueMapper function to return a null value
  • Stream.findFirst() and Stream.findAny() throw NPE if the value found is null

So, I think that disallowing null in the new JDK9 immutable collections (and maps) goes in the same direction.

Solution 7 - Java

The documentation does not say why null is not allowed:

> They disallow null keys and values. Attempts to create them with null keys or values result in NullPointerException.

In my opinion, the Map.of() and Map.ofEntries() static factory methods, which are going to produce a constant, are mostly formed by a developer at the compile type. Then, what is the reason to keep a null as the key or the value?

Whereas, the Map#put is usually used by filling a map at runtime where null keys/values could occur.

Solution 8 - Java

Not all Maps allow null as key

The reason mentioned in the docs of Map.

>Some map implementations have restrictions on the keys and values they may contain. For example, some implementations prohibit null keys and values, and some have restrictions on the types of their keys. Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionhi.nitishView Question on Stackoverflow
Solution 1 - JavaNicolai ParlogView Answer on Stackoverflow
Solution 2 - JavaEugeneView Answer on Stackoverflow
Solution 3 - JavaPedro BorgesView Answer on Stackoverflow
Solution 4 - Java1615903View Answer on Stackoverflow
Solution 5 - JavaGhostCatView Answer on Stackoverflow
Solution 6 - JavafpsView Answer on Stackoverflow
Solution 7 - JavaAndrew TobilkoView Answer on Stackoverflow
Solution 8 - JavaSuresh AttaView Answer on Stackoverflow