In Java, thread safety is an important concern when multiple threads access shared data structures. Two commonly used thread-safe map implementations are Hashtable and Synchronized Map. Although both provide synchronization, they differ in design, performance, and usage.
Hashtable
Hashtable is a legacy class that implements the Map interface. It stores data in the form of key–value pairs and is synchronized by default, meaning all its public methods are thread-safe.
- Does not allow null keys or null values.
- Uses hashCode() and equals() methods to store and retrieve elements.
- Provides Enumeration (not fail-fast) and Iterator (fail-fast).
- Default initial capacity is 11 with a load factor of 0.75.
import java.util.Hashtable;
import java.util.Map;
public class HashtableExample {
public static void main(String[] args) {
Hashtable<Integer, String> table = new Hashtable<>();
table.put(1, "James Bond");
table.put(2, "Donald Trump");
table.put(3, "Joe Biden");
for (Map.Entry<Integer, String> entry : table.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
Output
3 Joe Biden 2 Donald Trump 1 James Bond
Explanation:
- Creates a Hashtable that stores Integer keys and String values, and is thread-safe by default.
- Inserts key-value pairs using the put() method; null keys or values are not allowed.
- Uses entrySet() to iterate over all key–value pairs in the table.
- Prints each key and its corresponding value; the order of output is not guaranteed.
Synchronized Map
A Synchronized Map is created by wrapping a non-synchronized map (like HashMap) using Collections.synchronizedMap(). This provides thread safety by synchronizing access to the map.
- Synchronization is applied at the object level.
- Entire map is locked during read/write operations.
- Allows one null key and multiple null values (because it wraps HashMap).
- Iterators are fail-fast.
- Requires external synchronization while iterating.
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
public class SynchronizedMapExample {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "Brad");
map.put(2, "Anil");
map.put(3, "Sachin");
Map<Integer, String> syncMap = Collections.synchronizedMap(map);
synchronized (syncMap) {
Iterator<Map.Entry<Integer, String>> itr =
syncMap.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<Integer, String> entry = itr.next();
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
}
Output
1 Brad 2 Anil 3 Sachin
Explanation:
- Creates a regular HashMap and then wraps it using Collections.synchronizedMap() to make it thread-safe.
- Synchronization is applied at the object level, so all read/write operations acquire a lock on the map.
- While iterating, the map is explicitly synchronized using synchronized (syncMap) to prevent concurrent modification.
- Uses a fail-fast iterator, which throws ConcurrentModificationException if the map is modified during iteration.
Hashtable vs SynchronizedHashMap
|                 Hashtable                              |                Synchronized HashMap                      |
|---|---|
Hashtable doesn’t allow even a single null key and null values. | Synchronized HashMap allows one null key and any number of null values. |
Iterators returned by Hashtable are fail-fast in nature and throw ConcurrentModificationException on concurrent modification. | Iterators returned by synchronized HashMap are fail-fast in nature. i.e they throw ConcurrentModificationException if the map is modified after the creation of iterator. |
HashTable was there since JDK 1.1. From JDK 1.2, it has been made a part of Java Collection Framework. | HashMap is introduced in JDK 1.2. |
HashTable is the legacy class. It is sometimes considered as due for deprecation. So, it is recommended that not to use HashTable in your applications. | If you want a high level of data consistency, then only consider using synchronized HashMap. |