Q&A
HashMap vs. Hashtable in Java
Overview
Hashtable was introduced in Java 1.0; HashMap arrived in Java 2 (Collections Framework) with a cleaner API. They solve the same problem but embody different trade-offs around null handling and thread-safety. In modern code you almost always choose HashMap, and for concurrent access you reach for ConcurrentHashMap - Hashtable's synchronization design is considered obsolete.
graph TD
A[Key-value map in Java] --> B[HashMap: fast, single-threaded]
A --> C[Hashtable: synchronized, legacy]
A --> D[ConcurrentHashMap: concurrent, modern]
B --> B1[null key: 1 allowed<br/>null values: allowed]
C --> C1[null key/value: NOT allowed]
D --> D1[null key/value: NOT allowed]
style B fill:#1e1b4b,color:#fff
style D fill:#6366f1,color:#fff
Feature Comparison
| Feature | HashMap |
Hashtable |
|---|---|---|
| Thread-safe | No | Yes (synchronized methods) |
| Null keys | One allowed | Not allowed (NPE) |
| Null values | Allowed | Not allowed (NPE) |
| Performance | Fast (no lock overhead) | Slower (global lock) |
| Iteration order | No guarantee | No guarantee |
| Inheritance | AbstractMap |
Dictionary (legacy) |
| Introduced | Java 2 | Java 1.0 |
| Modern choice? | Yes | No (legacy) |
HashMap Example
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put(null, 99); // null key allowed
map.put("banana", null); // null value allowed
map.get(null); // 99
System.out.println(map.size()); // 3
map.forEach((k, v) -> System.out.println(k + "=" + v));
Hashtable Example
Map<String, Integer> legacy = new Hashtable<>();
legacy.put("apple", 1);
legacy.put(null, 1); // ❌ NullPointerException
legacy.put("key", null);// ❌ NullPointerException
Why Hashtable's Synchronization Is Obsolete
Hashtable uses a single lock on the entire map - every get, put, remove acquires it. Under concurrent load this becomes a bottleneck since only one thread can touch the map at a time. ConcurrentHashMap (Java 5+) uses segment locking (Java 7) and later lock-free reads + CAS writes (Java 8+) - dramatically better concurrency.
// Modern concurrent map:
Map<String, Integer> concurrent = new ConcurrentHashMap<>();
concurrent.put("key", 1);
// null keys/values not allowed, but concurrent access is efficient
HashMap Is Not Thread-Safe - Consequences
If multiple threads modify a HashMap concurrently without external synchronization:
- Race conditions, corrupted internal state, infinite loops.
- Fix: use
ConcurrentHashMap, orCollections.synchronizedMap(new HashMap<>())(coarse lock, like Hashtable but with proper iteration semantics), or external synchronization.
Map<String, Integer> safe = Collections.synchronizedMap(new HashMap<>());
// or, better:
Map<String, Integer> safe = new ConcurrentHashMap<>();
Iteration
HashMap.keySet(), values(), entrySet() return fail-fast iterators that throw ConcurrentModificationException if the map is modified during iteration (except through the iterator's remove). Hashtable's Enumeration does not fail-fast but is also outdated; modern code uses the iterator/stream APIs.
When to Use Each
HashMap- the default for single-threaded or externally-synchronized code.ConcurrentHashMap- concurrent access without null keys/values (don't need Hashtable).Hashtable- only when maintaining a legacy API that explicitly requires it.LinkedHashMap-HashMapwith insertion-order iteration.TreeMap- sorted by key (natural order or Comparator), O(log n) operations.
Why This Matters in Interviews
Java collection interviews ask this constantly. The three key points: null handling (HashMap allows null key/value; Hashtable throws NPE), synchronization (Hashtable is synchronized/slow; HashMap is not, but that's usually fine since concurrent code should use ConcurrentHashMap), and modernity (Hashtable is legacy). Explaining why ConcurrentHashMap beats Hashtable (segment/CAS vs whole-map lock) and knowing Collections.synchronizedMap as an alternative shows depth.
Common Pitfalls
- Choosing
Hashtablefor thread safety in new code (useConcurrentHashMapinstead). - Using
HashMapin a concurrent context without synchronization (data corruption). - Expecting
HashMapiteration to be ordered (it's not; useLinkedHashMap). - Calling
put(null, v)orput(k, null)onHashtableorConcurrentHashMapand getting NPE. - Confusing
HashMap(unsynchronized) withConcurrentHashMap(concurrent): they're both injava.utilbut not interchangeable for concurrent use.