Definition
- HashMap/LinkedHashMap: one null key (stored in bucket 0, hash = 0), multiple null values
- TreeMap: null key throws NullPointerException because
compareTo()orComparator.compare()is called on the key, and null has no natural ordering - TreeMap values: null values are allowed (values are not compared)
- ConcurrentHashMap/Hashtable: no null keys, no null values (ambiguity in concurrent context)
- Workaround:
new TreeMap<>(Comparator.nullsFirst(Comparator.naturalOrder()))accepts null keys
HashMap has a special "unlabelled drawer" (null key) – the bank allows exactly one such drawer. TreeMap is an alphabetically sorted filing system, and null has no alphabetical position – does it go before A? After Z? The system cannot compare it to anything, so it rejects null entirely. ConcurrentHashMap rejects null because when get() returns null, you cannot tell if the key is absent or maps to null.
Understanding the Concepts
Which Maps allow null keys?
| Map Type | Null key | Null value |
|---|---|---|
| HashMap | 1 allowed (bucket 0) | Multiple allowed |
| LinkedHashMap | 1 allowed (bucket 0) | Multiple allowed |
| TreeMap | Not allowed (NPE) | Multiple allowed |
| Hashtable | Not allowed (NPE) | Not allowed (NPE) |
| ConcurrentHashMap | Not allowed (NPE) | Not allowed (NPE) |
| EnumMap | Not allowed (NPE) | Multiple allowed |
| IdentityHashMap | 1 allowed | Multiple allowed |
package com.bank.demo;
import java.math.BigDecimal;
import java.util.*;
public class NullKeyDemo {
public static void main(String[] args) {
// HashMap: null key goes to bucket 0
Map<String, BigDecimal> hashMap = new HashMap<>();
hashMap.put(null, new BigDecimal("1000000"));
hashMap.put("SAV2001", null); // null value OK
System.out.println("HashMap null key: " + hashMap.get(null));
System.out.println("HashMap null value: " + hashMap.get("SAV2001"));
// LinkedHashMap: same as HashMap
Map<String, BigDecimal> linkedMap = new LinkedHashMap<>();
linkedMap.put(null, BigDecimal.ZERO);
System.out.println("LinkedHashMap null key: " + linkedMap.get(null));
// TreeMap: null key throws NPE
Map<String, BigDecimal> treeMap = new TreeMap<>();
treeMap.put("SAV2001", null); // null value OK
try {
treeMap.put(null, BigDecimal.ZERO); // NPE!
} catch (NullPointerException e) {
System.out.println("TreeMap null key: NullPointerException");
}
// ConcurrentHashMap: no null key or value
Map<String, BigDecimal> chm = new java.util.concurrent.ConcurrentHashMap<>();
try {
chm.put(null, BigDecimal.ZERO);
} catch (NullPointerException e) {
System.out.println("CHM null key: NullPointerException");
}
try {
chm.put("SAV2001", null);
} catch (NullPointerException e) {
System.out.println("CHM null value: NullPointerException");
}
}
}
HashMap null key: 1000000
HashMap null value: null
LinkedHashMap null key: 0
TreeMap null key: NullPointerException
CHM null key: NullPointerException
CHM null value: NullPointerException
Why does HashMap store null key in bucket 0?
HashMap's internal hash() method special-cases null:
static final int hash(Object key) {
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
When the key is null, the hash is 0. Bucket index = 0 & (capacity - 1) = 0. So the null key always goes to bucket 0.
Why does TreeMap reject null keys?
TreeMap uses compareTo() (or a Comparator's compare()) to position entries in the Red-Black tree. When you call null.compareTo(anything), Java throws NullPointerException. There is no natural ordering position for null.
The exception occurs on the second put(), not the first. The first entry has nothing to compare against and is placed at the root. The second entry must be compared with the root key, and if either is null, the comparison fails.
// TreeMap internal put logic (simplified)
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
// First entry: no comparison needed
root = new Entry<>(key, value, null);
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
// Comparator: cpr.compare(key, t.key) – NPE if key is null!
} else {
// Natural ordering: ((Comparable<K>)key).compareTo(t.key) – NPE if key is null!
}
}
How do you allow null keys in TreeMap?
Use Comparator.nullsFirst() or Comparator.nullsLast() to define where null should sort:
package com.bank.demo;
import java.util.*;
public class TreeMapNullWorkaround {
public static void main(String[] args) {
// Null sorts before all other keys
TreeMap<String, String> nullFirst = new TreeMap<>(
Comparator.nullsFirst(Comparator.naturalOrder())
);
nullFirst.put(null, "Default");
nullFirst.put("SAV2001", "Rajesh");
nullFirst.put("CUR3001", "Priya");
System.out.println("nullsFirst: " + nullFirst);
// Null sorts after all other keys
TreeMap<String, String> nullLast = new TreeMap<>(
Comparator.nullsLast(Comparator.naturalOrder())
);
nullLast.put(null, "Default");
nullLast.put("SAV2001", "Rajesh");
System.out.println("nullsLast: " + nullLast);
}
}
nullsFirst: {null=Default, CUR3001=Priya, SAV2001=Rajesh}
nullsLast: {SAV2001=Rajesh, null=Default}
What is the best practice for null handling in maps?
Avoid null keys and null values. Use sentinel objects or Optional instead.
// Instead of null key:
private static final String DEFAULT_KEY = "__DEFAULT__";
map.put(DEFAULT_KEY, defaultValue);
// Instead of null value:
map.put("SAV2001", Optional.empty()); // explicit "no value"
// Or use getOrDefault:
BigDecimal balance = map.getOrDefault("UNKNOWN", BigDecimal.ZERO);
What should a senior engineer know?
- Migration risk. Code that uses HashMap with null keys will break when migrated to ConcurrentHashMap or TreeMap. Always search for null key usage before switching map implementations.
- containsKey vs get for null. get(key) returning null is ambiguous: key absent or value is null. Use containsKey(key) to distinguish. But in concurrent maps, this check-then-act is racy, which is why ConcurrentHashMap rejects null entirely.
What are common interview follow-up questions?
- "How does HashMap store the null key?" – Hash 0, bucket 0.
- "Why does TreeMap reject null keys?" – compareTo throws NPE on null.
- "Why does ConcurrentHashMap reject null values?" – get() returning null would be ambiguous.
- "How do you allow null keys in TreeMap?" – Comparator.nullsFirst() or nullsLast().
Visual Overview
Null key handling HashMap: null → hash(null) = 0 → bucket 0 LinkedHashMap: null → hash(null) = 0 → bucket 0 TreeMap: null → compareTo(?) → NPE! ConcurrentHashMap: null → rejected (ambiguous) Hashtable: null → rejected (legacy) TreeMap workaround: Comparator.nullsFirst(naturalOrder()) → null sorts before all keys
Key Takeaway
HashMap and LinkedHashMap allow one null key (stored in bucket 0) and any number of null values. TreeMap rejects null keys because it must compare every key using compareTo or a Comparator, and null has no natural ordering. ConcurrentHashMap rejects both null keys and null values to avoid ambiguity in concurrent contexts.
The best practice is to avoid null keys and values entirely. Use sentinel objects, Optional, or getOrDefault() instead. If you must support null keys in a sorted map, use Comparator.nullsFirst(naturalOrder()) to define where null should sort.
In the National Bank of India system, Rajesh enforces Objects.requireNonNull(accountId) at every service entry point, ensuring that null never reaches a map key. This eliminates an entire category of bugs and makes the code safe to migrate between HashMap, TreeMap, and ConcurrentHashMap without surprises.
Remember one sentence: HashMap allows one null key in bucket 0; TreeMap rejects null because compareTo cannot order it; avoid null keys entirely for clean, portable code.
---