Back to LinkedHashMap, TreeMap & Map Comparisons
Q5Question 5 of 6

How does HashMap handle null keys?

Key Concepts
  • HashMap allows exactly one null key, stored in bucket 0 with hash value 0
  • Multiple null values are allowed; any number of keys can map to null
  • A second put with null key overwrites the first; put returns the old value
  • Internally, HashMap special-cases null in its hash function: hash(null) returns 0
  • Hashtable and ConcurrentHashMap reject null keys with NullPointerException
  • In practice, avoid null keys; use a sentinel string like "__DEFAULT__" for clarity

Definition

HashMap allows exactly one null key (stored in bucket 0 with hash 0) and any number of null values; a second null-key put overwrites the first.
  • 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:

JAVA
// 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.

JAVA
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
    }
}
TEXT
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

JAVA
// 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate