How do HashMap, Hashtable and ConcurrentHashMap differ in Java?
All three map keys to values, but they differ in three dimensions: thread safety, null handling, and age.
HashMap is not thread safe and allows a null key and null values. It is the default choice for single-threaded code, and it is faster than the alternatives because it does no synchronisation at all.
Hashtable is the old class, dating back to the earliest releases of the language. Every method is synchronised, so it is thread safe, and it rejects null keys and null values with a NullPointerException. It also has a legacy elements() enumeration API and, importantly, it uses equals and hashCode the same way HashMap does, so it is otherwise a drop-in substitute.
ConcurrentHashMap is the modern answer for concurrent use. It is thread safe without synchronising the whole map on every operation — reads are lock-free and writes lock only the bin being modified. It rejects null keys and null values, because under concurrency a null is ambiguous: it could mean "not present yet" or "explicitly absent", and there is no safe way to distinguish those without a lock.
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> hashtable = new Hashtable<>();
Map<String, Integer> concurrent = new ConcurrentHashMap<>();
hashMap.put("a", 1);
hashtable.put("a", 1);
concurrent.put("a", 1);
System.out.println("sizes : " + hashMap.size() + ", " + hashtable.size() + ", " + concurrent.size());
System.out.println("get : " + hashMap.get("a") + hashtable.get("a") + concurrent.get("a"));
// Hashtable rejects null keys.
try {
hashtable.put(null, 1);
} catch (NullPointerException e) {
System.out.println("hashtable null key -> NullPointerException");
}
// ConcurrentHashMap rejects null keys and null values.
try {
concurrent.put(null, 1);
} catch (NullPointerException e) {
System.out.println("concurrent null key -> NullPointerException");
}
// HashMap allows both.
hashMap.put(null, 5);
System.out.println("hashmap null key ok : " + hashMap.get(null));
}
}The output shows the three behaviours side by side. All three maps hold the same single entry, Hashtable and ConcurrentHashMap both refuse a null key with a NullPointerException, and HashMap accepts one without complaint.
The practical rule: HashMap for single-threaded code, ConcurrentHashMap for concurrent code. Hashtable is legacy — it still works, but there is no reason to choose it over ConcurrentHashMap unless you are working with very old code.