Definition
- One null key: HashMap special-cases null in its
hash()function, returning 0; the null key always goes to bucket 0 - Multiple null values: any key can map to null; there is no restriction on null values
- Overwrite: putting a second entry with null key replaces the first; the map cannot have duplicate keys
- get(null): returns the value mapped to the null key, or null if no null key exists (ambiguous!)
- Other maps: Hashtable, ConcurrentHashMap, and TreeMap (with natural ordering) reject null keys with NullPointerException
Think of a filing cabinet where one special drawer has no label at all – the null key drawer. The bank allows exactly one unlabelled drawer. You can store anything in it, and you can also store "nothing" (null value) in any labelled drawer. But there is only ever one unlabelled drawer.
Understanding the Concepts
How does HashMap handle null keys?
HashMap's hash() method explicitly checks for null:
// From OpenJDK HashMap source
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
When the key is null, the hash is 0. The bucket index is 0 & (capacity - 1) = 0. So the null key always resides in bucket 0.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.*;
public class NullKeyValueDemo {
public static void main(String[] args) {
Map<String, BigDecimal> balances = new HashMap<>();
// One null key allowed
balances.put(null, new BigDecimal("1000000"));
System.out.println("Null key value: " + balances.get(null));
// Second null key overwrites
BigDecimal old = balances.put(null, new BigDecimal("250000"));
System.out.println("Old value: " + old);
System.out.println("New value: " + balances.get(null));
// Multiple null values allowed
balances.put("SAV2001", null);
balances.put("CUR3001", null);
balances.put("FD4001", new BigDecimal("500000"));
System.out.println("SAV2001: " + balances.get("SAV2001")); // null
System.out.println("CUR3001: " + balances.get("CUR3001")); // null
// Ambiguity: get returns null for both absent key and null-valued key
System.out.println("Absent: " + balances.get("UNKNOWN")); // null
System.out.println("Null-valued: " + balances.get("SAV2001")); // null
// How to distinguish? Use containsKey:
System.out.println("Contains UNKNOWN: " + balances.containsKey("UNKNOWN")); // false
System.out.println("Contains SAV2001: " + balances.containsKey("SAV2001")); // true
}
}
Null key value: 1000000
Old value: 1000000
New value: 250000
SAV2001: null
CUR3001: null
Absent: null
Null-valued: null
Contains UNKNOWN: false
Contains SAV2001: true
Why do ConcurrentHashMap and Hashtable reject null?
In a concurrent context, get(key) returning null is ambiguous. Does the key not exist, or does it map to null? In a single-threaded HashMap, you can call containsKey() right after get() to resolve this. In a concurrent map, another thread can modify the map between the two calls, making the result unreliable.
Doug Lea (author of ConcurrentHashMap) chose to reject null entirely to avoid this ambiguity.
What are common mistakes?
Mistake: Relying on get() returning null to check absence
// WRONG: null means absent OR null-valued
BigDecimal balance = map.get(accountId);
if (balance == null) {
// Is the account absent? Or does it have null balance?
// Cannot tell from get() alone!
}
// CORRECT: use containsKey to distinguish
if (map.containsKey(accountId)) {
BigDecimal balance = map.get(accountId); // may be null but key exists
} else {
// key truly absent
}
// BETTER: use getOrDefault
BigDecimal balance = map.getOrDefault(accountId, BigDecimal.ZERO);
What should a senior engineer know?
- Migration hazard. Code using null keys in HashMap will throw NPE when migrated to ConcurrentHashMap or TreeMap. Always audit for null key usage before switching implementations.
- Null key collisions. The null key shares bucket 0 with other entries whose hash maps to bucket 0. In a large table, this is unlikely to cause performance issues, but it is worth knowing.
- Serialization. HashMap correctly serializes and deserializes null keys and null values.
What are common interview follow-up questions?
- "How many null keys can HashMap hold?" – Exactly one.
- "Where is the null key stored?" – Bucket 0, hash value 0.
- "Why does ConcurrentHashMap reject null?" – get() returning null would be ambiguous in concurrent context.
- "How do you check if a key exists when values can be null?" – Use containsKey().
Visual Overview
HashMap null key handling
hash(null) → 0
bucket index: 0 & (capacity - 1) = 0
Bucket 0: [null → 250000] → [other entries with hash 0]
Bucket 1: [SAV2001 → null] → ...
Bucket 2: [CUR3001 → null] → ...
Bucket 5: [FD4001 → 500000]
get(null) → bucket 0 → find null key → return 250000
get("UNKNOWN") → bucket X → not found → return null
get("SAV2001") → bucket 1 → found → return null
// Both absent and null-valued return null! Use containsKey to distinguish.
Key Takeaway
HashMap permits exactly one null key and any number of null values. The null key is special-cased with hash 0 and always stored in bucket 0. A second put with null key overwrites the first, just like any duplicate key.
The ambiguity between "key absent" and "key maps to null" is a common source of bugs. Always use containsKey() to check existence when null values are possible, or better yet, use getOrDefault() to provide a safe default.
In the National Bank of India system, Rajesh avoids null keys and null values entirely. He uses Objects.requireNonNull() at service boundaries and BigDecimal.ZERO as a sentinel instead of null for "no balance computed yet." This makes the code safe to migrate between HashMap, TreeMap, and ConcurrentHashMap without null-related surprises.
Remember one sentence: HashMap stores one null key in bucket 0, but get() returning null is ambiguous – use containsKey() or avoid null keys altogether.
---