🔑 Section 10 · Question #24

Can HashMap Store a null Key

In java.util.HashMap, the hash function explicitly guards against null using a ternary check:


🟢 Junior Level

Yes, HashMap permits storing a null key, but only as a single unique entry.

Key Rules & Behaviors:

  1. Uniqueness: Just like any other key in HashMap, duplicates are not allowed. Calling map.put(null, "new value") a second time simply overwrites the previous value associated with null.
  2. Fixed Bucket Zero (table[0]): The hash code for a null key is hardcoded to 0. Consequently, (n - 1) & 0 always evaluates to 0, placing the entry strictly into bucket 0 (table[0]).
  3. Null-Safe Operations: Storing or retrieving a null key does not throw a NullPointerException because HashMap never invokes .hashCode() or .equals() on null.
import java.util.HashMap;
import java.util.Map;

public class NullKeyDemo {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();

        map.put(null, "Default Value");
        System.out.println(map.get(null)); // "Default Value"

        // Overwriting the existing null key
        map.put(null, "Updated Value");
        System.out.println(map.get(null)); // "Updated Value"
        System.out.println(map.size());    // 1 (size does not grow)
    }
}

Analogy: A null key is like a dedicated “Uncategorized” folder on the very first shelf (drawer 0) of a filing cabinet. You can only have one such folder; placing a new document inside replaces the previous one.


🟡 Middle Level

The Hash Function for null

In java.util.HashMap, the hash function explicitly guards against null using a ternary check:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

The bucket index calculation uses the power-of-two bitwise mask:

\[\text{index} = (n - 1) \ \& \ \text{hash} = (n - 1) \ \& \ 0 = 0\]

Therefore, a null key is deterministic and invariably mapped to table[0].

Node Lookup and Equality Verification

Bucket table[0] is not isolated; non-null keys with hash codes that evaluate to index 0 can share bucket 0. When traversing the bucket during get(null) or put(null, value), HashMap evaluates:

if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))

When searching for key = null:

  • hash is 0, matching p.hash == 0.
  • The reference check (k = p.key) == key evaluates p.key == null.
  • Because key != null is false, short-circuit evaluation prevents key.equals(k) from ever being called.
  • No NullPointerException can occur.

Map Implementation Comparison

Map Implementation null Key Allowed null Values Allowed Behavior on put(null, ...)
HashMap ✅ Yes (Single key) ✅ Yes Stored at table[0]
LinkedHashMap ✅ Yes (Single key) ✅ Yes Stored at table[0] + linked in iteration list
ConcurrentHashMap ❌ No ❌ No Throws NullPointerException immediately
Hashtable ❌ No ❌ No Throws NullPointerException (key.hashCode())
TreeMap ⚠️ Conditionally ✅ Yes Throws NPE by default; allowed if custom comparator handles null
Map.of(...) (Java 9+) ❌ No ❌ No Throws NullPointerException (strict contract)

🔴 Senior Level

Evolution of null Handling: Java 7 vs Java 8+

In Java 7, HashMap maintained dedicated internal helper methods for null keys:

  • putForNullKey(V value)
  • getForNullKey()

Java 7 bypassed the general hashing loop for null to avoid conditional checks inside the core traversal routines.

In Java 8+, the implementation was unified:

  • hash(key) returns 0 when key == null.
  • The standard execution path (putVal, getNode, removeNode) processes null identically to any other key with hash 0.
  • The specialized putForNullKey and getForNullKey methods were removed from OpenJDK.

Myth: Bucket table[0] Is Exclusively Reserved for null

A common misconception is that table[0] belongs solely to the null key. In reality:

  1. Any object where hash(key) == 0 lands in bucket 0 (e.g., "" or custom objects returning 0).
  2. Any key whose low-order bits match 0 modulo table capacity lands in table[0] (e.g., hash code 16, 32, 48 when capacity is 16).
  3. If 8 or more colliding non-null keys enter table[0] alongside null, and the map’s capacity is $\ge 64$, bucket 0 will treeify into a Red-Black Tree (TreeNode)!
  4. Inside a treeified bucket 0, the null node becomes a TreeNode. Because null does not implement Comparable, tree balancing resolves ordering via the fallback method tieBreakOrder(a, b) using System.identityHashCode(a).

Architectural Risks & Production Pitfalls

  1. Refactoring to Concurrent Collections: If application logic permits null keys in HashMap, later refactoring to ConcurrentHashMap for thread safety causes silent runtime NullPointerException failures.
  2. Incompatibility with Modern Java APIs: Immutable map factories (Map.of(), Map.ofEntries()) and stream collectors (Collectors.toUnmodifiableMap()) strictly prohibit null keys and values via Objects.requireNonNull().
  3. Domain-Driven Design (DDD) Violation: In business domains, a Map key models a domain identity (e.g., CustomerId, TransactionUuid). A missing identity (null) represents an invalid state or uninitialized entity, which should never be used as a lookup index.
// Anti-pattern:
Map<String, Handler> handlers = new HashMap<>();
handlers.put(null, defaultHandler);

// Clean DDD Pattern:
public enum RouteKey {
    STANDARD,
    PREMIUM,
    ANONYMOUS_FALLBACK
}

Map<RouteKey, Handler> safeHandlers = new ConcurrentHashMap<>();
safeHandlers.put(RouteKey.ANONYMOUS_FALLBACK, defaultHandler);

4 Tricky Questions

1. Can a TreeMap store a null key?

Answer: By default, no; but with a custom Comparator, yes. By default, new TreeMap<>() uses the natural ordering of keys (Comparable). During the first or subsequent insertions, k.compareTo(...) is called, throwing an immediate NullPointerException. However, if the TreeMap is instantiated with a null-friendly comparator, it works seamlessly:

Map<String, String> treeMap = new TreeMap<>(Comparator.nullsFirst(Comparator.naturalOrder()));
treeMap.put(null, "Safe Value");
System.out.println(treeMap.get(null)); // "Safe Value"

The comparator intercepts null prior to invoking methods on the object, positioning null at the extreme left (or right with nullsLast) of the Red-Black Tree.


2. What happens if you wrap a HashMap containing a null key with Collections.unmodifiableMap()?

Answer: The code executes normally without exceptions. Collections.unmodifiableMap is a lightweight wrapper decorator. It forwards read calls directly to the backing map and blocks mutating operations (put, remove, clear). If the underlying HashMap contains a null key, unmodifiableMap.get(null) and unmodifiableMap.containsKey(null) work as expected.


3. Can bucket table[0] containing a null key treeify into a TreeNode?

Answer: Yes. If 8 or more entries with hash codes resolving to index 0 are added to table[0] alongside null, and the total capacity of the map is at least 64 (MIN_TREEIFY_CAPACITY), table[0] treeifies. The entry with the null key is converted into a TreeNode. When traversing or inserting into the tree, because null does not implement Comparable, HashMap falls back to tieBreakOrder(a, b) using System.identityHashCode().


4. Why does Map.of(null, "value") throw an NPE while HashMap permits null?

Answer: Modern Java APIs (Java 9+) follow stricter defensive programming standards. Brian Goetz and the OpenJDK team considered the permission of null in legacy collections (Java 1.2) to be an evolutionary mistake (“billion-dollar mistake”) that fosters subtle bugs, ambiguate map queries (get(k) == null meaning key absent vs mapped to null), and hinders internal layout optimizations (e.g., Valhalla value objects). Hence, all modern factories (List.of, Set.of, Map.of) enforce Objects.requireNonNull.


🎯 Interview Cheat Sheet

  • Permission: HashMap allows exactly one null key. Inserting again overwrites the previous value.
  • Bucket Location: Always mapped to table[0] because hash(null) == 0 and (n - 1) & 0 == 0.
  • Comparison: Evaluated via reference equality p.key == null. .equals() is never invoked on null, preventing NullPointerException.
  • Bucket Zero Is Shared: table[0] is not reserved for null; non-null keys with colliding hash codes can reside in and even treeify table[0].
  • Other Collections:
    • ConcurrentHashMap, Hashtable: Strictly prohibit null keys (throws NPE).
    • TreeMap: Throws NPE by default; permitted only with Comparator.nullsFirst(...).
    • Map.of(...): Strictly prohibits null keys (throws NPE).
  • Production Best Practice: Avoid null keys. Use the Null Object pattern or sentinel enum constants to ensure seamless migration to concurrent collections and modern immutable APIs.