"Difference between HashMap and Hashtable?" has been a Java interview staple for two decades. The twist most candidates miss: the best answer ends with "…and in new code I would use neither for concurrency — I would use ConcurrentHashMap." This article gives you the full comparison and that finishing move.
Verdict at a glance
| Dimension | HashMap | Hashtable |
|---|---|---|
| Since | Java 1.2 (Collections Framework) | Java 1.0 (legacy) |
| Synchronization | none | every method synchronized |
| Thread-safe | no | yes (coarse-grained) |
| Null key | one allowed | not allowed (NPE) |
| Null values | allowed | not allowed (NPE) |
| Iterator | fail-fast Iterator |
Iterator + legacy Enumeration |
| Performance | fast | slow under any contention |
| Parent class | AbstractMap |
Dictionary (obsolete) |
| Treeification (Java 8+) | yes | no |
| Modern replacement | — | ConcurrentHashMap |
Verdict: use HashMap for single-threaded or externally synchronized code, ConcurrentHashMap for shared mutable state, and Hashtable only when maintaining code that already depends on it.
Origin story: why both exist
Hashtable shipped with Java 1.0 in 1996, before the Collections Framework existed. It extends the obsolete Dictionary class and made every method synchronized because early Java leaned heavily on threads.
Java 1.2 introduced the Collections Framework with a different philosophy: collections are unsynchronized by default, and you add thread safety only where needed. HashMap was the clean-slate redesign. Hashtable was retrofitted to implement Map so old code kept compiling, but it has been a legacy citizen ever since — the JDK's own Javadoc points users to HashMap and ConcurrentHashMap.
Difference 1: synchronization and what it really buys
Every public method of Hashtable — get, put, size, all of them — locks the entire table on this. Two consequences:
It is slow. Threads serialize on a single lock even when they only read. On a modern multi-core machine, that lock becomes the bottleneck long before the hashing does.
It is less safe than it looks. Individual calls are atomic, but real code composes calls — and compositions are not protected:
import java.util.Hashtable;
import java.util.Map;
public class CheckThenActRace {
private static final Map<String, Integer> seats = new Hashtable<>();
public static void main(String[] args) throws InterruptedException {
seats.put("A1", 0);
Runnable book = () -> {
// check-then-act: NOT atomic even on a Hashtable
if (seats.get("A1") == 0) {
try { Thread.sleep(10); } catch (InterruptedException e) { }
seats.put("A1", 1);
System.out.println(Thread.currentThread().getName()
+ " booked seat A1");
}
};
Thread t1 = new Thread(book, "user-1");
Thread t2 = new Thread(book, "user-2");
t1.start(); t2.start();
t1.join(); t2.join(); // frequently BOTH threads "book" the seat
}
}
Run it a few times: both threads regularly pass the get() check before either writes, and the seat gets double-booked. The synchronized methods did not help, because the race spans two calls. ConcurrentHashMap fixes this class of bug with atomic compound operations like putIfAbsent() and compute() — see HashMap vs ConcurrentHashMap.
Interview note: This is the trap hidden inside "Hashtable is thread-safe." Method-level atomicity does not give you transaction-level atomicity. Pointing that out — ideally with the check-then-act example — moves your answer from "memorized" to "understood".
Difference 2: null handling
HashMap permits one null key (stored in bucket 0) and any number of null values. Hashtable throws NullPointerException for both:
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class NullHandling {
public static void main(String[] args) {
Map<String, String> hashMap = new HashMap<>();
hashMap.put(null, "null key is fine");
hashMap.put("config", null);
System.out.println(hashMap.get(null)); // null key is fine
Map<String, String> table = new Hashtable<>();
table.put("ok", "value");
try {
table.put(null, "boom");
} catch (NullPointerException e) {
System.out.println("Hashtable rejected null key");
}
try {
table.put("key", null);
} catch (NullPointerException e) {
System.out.println("Hashtable rejected null value");
}
}
}
The null-value ban is not arbitrary. In a concurrent map, get(k) returning null is ambiguous — absent key or stored null? Single-threaded code can disambiguate with containsKey(), but in a concurrent map another thread may change the answer between the two calls. ConcurrentHashMap bans nulls for exactly the same reason.
Difference 3: iteration and fail-fast behavior
HashMap iterators are fail-fast: structurally modify the map mid-iteration (outside Iterator.remove()) and you get a ConcurrentModificationException on a best-effort basis.
Hashtable offers the same fail-fast Iterator through its collection views, plus the pre-collections Enumeration via keys() and elements() — which is not fail-fast and can yield inconsistent views mid-modification. Code still using Enumeration is a reliable sign of a very old codebase.
Difference 4: performance and internals
Beyond locking, the implementations diverged over the years:
- Java 8 treeification:
HashMapconverts collision chains longer than 8 nodes into Red-Black trees, capping worst-case lookup at O(log n).Hashtablenever got this upgrade — a flood of colliding keys degrades it to O(n) scans. The mechanics are covered in how HashMap works internally. - Capacity strategy:
HashMapuses power-of-two capacities (default 16) with(n - 1) & hashindexing.Hashtabledefaults to 11, grows to2n + 1, and computes(hash & 0x7FFFFFFF) % capacity— a division on every access. - Hash spreading:
HashMapmixes high bits into low bits (h ^ (h >>> 16));Hashtableuses the rawhashCode().
Uncontended, HashMap is simply the faster and more robust design. Contended, Hashtable collapses because every thread queues on one lock.
Common mistake: A common mistake beginners make is "fixing" a race condition by swapping
HashMapforHashtable. It hides the crash but keeps the logic race (as the seat-booking demo shows) and throttles throughput. The honest fix isConcurrentHashMapwith atomic operations, or an explicit lock around the whole business operation.
What about Collections.synchronizedMap()?
Collections.synchronizedMap(new HashMap<>()) is essentially a modern-flavored Hashtable: a wrapper that synchronizes every call on a single mutex. Same coarse lock, same compound-operation problem, and you must manually synchronize during iteration. It exists for adapting legacy single-lock designs, not as a recommendation. For real concurrency the ladder is: HashMap (single thread) → ConcurrentHashMap (shared state). Hashtable and synchronizedMap sit on a rung nobody should climb to in new code.
Choose HashMap if / choose Hashtable if
Choose HashMap if:
- The map is confined to one thread, or guarded by your own higher-level lock.
- You need null values (e.g., caching "looked up, found nothing").
- You care about performance — which is nearly always.
- You are writing any new code. Pair it with the best practices for keys and capacity.
Choose Hashtable if:
- You are maintaining legacy code whose API exposes
HashtableorDictionaryand refactoring is out of scope. - A legacy library (old
Properties-style APIs —java.util.Propertiesitself extendsHashtable) forces it on you.
That second list is deliberately short. There is no performance, safety or feature scenario where Hashtable beats both HashMap and ConcurrentHashMap.
For freshers: how to answer this in an interview
A tight 30-second answer: "Both implement Map with a hash table. Hashtable is the legacy Java 1.0 class — every method is synchronized, nulls are forbidden, and it's slow under contention. HashMap is the Java 1.2 redesign — unsynchronized, allows one null key and null values, fail-fast iterators, and since Java 8 it treeifies long collision chains. In new code I'd use HashMap single-threaded and ConcurrentHashMap for shared state; Hashtable only survives for backward compatibility."
Expect these follow-ups, in rough order of likelihood: why nulls are banned in concurrent maps, why "thread-safe" doesn't make check-then-put safe, and how ConcurrentHashMap locks differently. The first two are answered above; the third has its own dedicated comparison. For a broader drill across the whole framework, work through the Java collections interview questions set.
Frequently Asked Questions
What is the main difference between HashMap and Hashtable?
Is Hashtable deprecated in Java?
Why does Hashtable not allow null keys or values?
Should I use Hashtable for thread safety?
Is compound code like check-then-put safe on a Hashtable?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

