Why Hashtable does not allow null keys or values?

The reason is the reason on the accepted answer: Hashtable is old.

However, the use of Hashtable IS NOT discouraged in favor of HashMap in every scenario.

  • Hashtable is synchronized, so it is THREAD-SAFE. HashMap is not.

Neither Hashtable nor ConcurrentHashMap support null keys or values. HashMap does.

If you want a drop-in replacement that doesn't require anything else than changing the class and works in every scenario, there is none. The most similar option would be ConcurrentHashMap (which is thread safe but doesn't support locking the whole table):

This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.

HashMap is a better replacement for single threaded applications or any time synchronization is not a requirement, because of the performance impact synchronization introduces.

Sources:

  • Hashtable
  • HashMap
  • ConcurrentHashMap

The main reason why Hashtable and ConcurrentHashMap do not allow null keys or values is because of the expectation that they are going to be used in a multi-threaded environment. For a minute, let's assume that null values are allowed. In this case the hashtable's "get" method has ambiguous behavior. It can return null if the key is not found in the map or it can return null if the key is found and its value is null. When a code expects null values, it usually checks if the key is present in the map so that it can know whether the key is not present or the key is present but value is null. Now this code breaks in a multi-threaded environment. Let's take a look at below code:

if (map.contains(key)) {
    return map.get(key);
} else {
    throw new KeyNotFoundException;
}

In the above code, let's say thread t1 calls the contains method and finds the key and it assumes that key is present and is ready for returning the value whether it is null or not. Now before it calls map.get, another thread t2 removes that key from the map. Now t1 resumes and returns null. However as per the code, the correct answer for t1 is KeyNotFoundException because the key has been removed. But still it returns the null and thus the expected behavior is broken.

Now, for a regular HashMap, it is assumed, that it is going to get called by a single thread, hence there is no possibility of key getting removed in the middle of "contains" check and "get". So HashMap can tolerate null values. However for Hashtable and ConcurrentHashMap, the expectations are clear that multiple threads are going to act on the data. Hence they cannot afford to allow null values and give out incorrect answer. Same logic goes for keys. Now the counter argument can be - the contains and get steps could fail for non null values for Hashtables and ConcurrentHashMaps, because another thread can modify the map/table before the second step gets executed. That is correct, it can happen. But since Hashtables and ConcurrentHashMaps do not allow null keys and values, it is not necessary for them to implement contains and get check in the first place. They can directly get the value because they know that if the get method returns null, the only reason for that is the key is not present and not because the value could be null. The contains and get check is necessary only for HashMaps because they allow the null values and thus need to resolve the ambiguity about whether the key is not found or the value is null.


Hashtable is the older class, and its use is generally discouraged. Perhaps they saw the need for a null key, and more importantly - null values, and added it in the HashMap implementation.

HashMap is newer, and has more advanced capabilities, which are basically just an improvement on the Hashtable functionality. When HashMap was created, it was specifically designed to handle null values as keys and handles them as a special case.

Edit

From Hashtable JavaDoc:

To successfully store and retrieve objects from a Hashtable, the objects used as keys must implement the hashCode method and the equals method.

Since null isn't an object, you can't call .equals() or .hashCode() on it, so the Hashtable can't compute a hash to use it as a key.