Java Notes
Complete guide to Java Hashtable — legacy synchronized map, internal working, comparison with HashMap and ConcurrentHashMap, and why you should avoid it in modern code.
What is Hashtable?
java.util.Hashtable is a legacy synchronized hash map that has been part of Java since JDK 1.0. It was later retrofitted to implement the Map interface when the Collections Framework was introduced in Java 1.2.
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, SerializableKey point: Hashtable is essentially a synchronized HashMap with the additional restriction that neither keys nor values can be null.
Synchronization
Every method in Hashtable is synchronized:
public synchronized V put(K key, V value) {
if (value == null) throw new NullPointerException(); // No null values!
// ... compute hash, find bucket, insert/replace
}
public synchronized V get(Object key) {
// ... compute hash, find bucket, walk chain
}
public synchronized V remove(Object key) {
// ... compute hash, find bucket, unlink entry
}
public synchronized boolean containsKey(Object key) { ... }
public synchronized boolean contains(Object value) { ... }
public synchronized int size() { ... }
public synchronized Enumeration<K> keys() { ... }This means one global lock on the entire table. While one thread reads, ALL other threads must wait — even if they're accessing different buckets.
Complete Example
import java.util.*;
public class HashtableDemo {
public static void main(String[] args) {
Hashtable<String, Integer> ht = new Hashtable<>();
// Basic operations
ht.put("Alice", 30);
ht.put("Bob", 25);
ht.put("Charlie", 35);
System.out.println("Get Alice: " + ht.get("Alice")); // 30
System.out.println("Size: " + ht.size()); // 3
System.out.println("Contains Bob: " + ht.containsKey("Bob")); // true
// Null NOT allowed (unlike HashMap)
try {
ht.put(null, 10); // NullPointerException!
} catch (NullPointerException e) {
System.out.println("Null key not allowed!");
}
try {
ht.put("Eve", null); // NullPointerException!
} catch (NullPointerException e) {
System.out.println("Null value not allowed!");
}
// Iteration with Enumeration (legacy)
Enumeration<String> keys = ht.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
System.out.println(key + " = " + ht.get(key));
}
// Iteration with entrySet (modern)
for (Map.Entry<String, Integer> entry : ht.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
}
}Hashtable: {C=Cherry, B=Banana, A=Apple}
Get A: Apple
Contains key B: true
Contains value Cherry: true
Size: 3
After remove B: {C=Cherry, A=Apple}Time Complexity
| Operation | Average | Worst Case |
|---|---|---|
| ----------- | :-: | :-: |
put(key, value) | O(1) | O(n) |
get(key) | O(1) | O(n) |
remove(key) | O(1) | O(n) |
containsKey(key) | O(1) | O(n) |
containsValue(value) | O(n) | O(n) |
size() | O(1) | O(1) |
Note: Unlike HashMap (Java 8+), Hashtable does NOT treeify long chains. Worst case remains O(n).
Plus: Every operation has synchronization overhead (monitor acquire/release).
Hashtable vs HashMap — Detailed Comparison
| Feature | Hashtable | HashMap |
|---|---|---|
| Introduced | JDK 1.0 | JDK 1.2 |
| Synchronization | Yes (every method) | No |
| Null keys | ❌ Not allowed | ✅ One null key |
| Null values | ❌ Not allowed | ✅ Multiple nulls |
| Extends | Dictionary (legacy) | AbstractMap |
| Initial capacity | 11 | 16 |
| Capacity constraint | Any size | Power of 2 |
| Growth | 2n + 1 | 2n (double) |
| Hashing | hashCode() % length | (length-1) & hash (faster) |
| Treeification | No | Yes (Java 8+) |
| Iterator | Fail-fast + Enumeration | Fail-fast only |
| Performance | Slower (synchronization) | Faster |
| Use in new code | ❌ Never | ✅ Yes |
Hashtable vs ConcurrentHashMap
| Feature | Hashtable | ConcurrentHashMap |
|---|---|---|
| Lock granularity | Entire table (one lock) | Bucket-level (segment/node locks) |
| Concurrent reads | Blocked by any write | Non-blocking (volatile reads) |
| Concurrent writes | Fully serialized | Concurrent on different buckets |
| Null keys/values | Not allowed | Not allowed |
| Treeification | No | Yes (Java 8+) |
| Performance (multi-threaded) | Poor | Excellent |
| CAS operations | No | Yes (lock-free where possible) |
| Recommended | ❌ Never | ✅ Yes |
Performance Visualization
| Threads | 1 2 4 8 16 |
| Hashtable | 100% 60% 35% 20% 12% (throughput) |
| ConcurrentHashMap | 100% 190% 370% 700% 1300% (throughput scales!) |
ConcurrentHashMap allows multiple threads to read/write concurrently to different segments, while Hashtable forces all operations through a single lock.
Why Hashtable is Obsolete
1. Coarse-Grained Locking
2. No Treeification
HashMap (Java 8+) converts long chains to Red-Black trees. Hashtable chains remain as linked lists, so worst-case is O(n) with hash collisions.
3. Compound Operations Aren't Atomic
// NOT SAFE — even with Hashtable's synchronization!
Hashtable<String, Integer> ht = new Hashtable<>();
// Thread 1 and 2 racing:
if (!ht.containsKey("counter")) { // Check
ht.put("counter", 1); // Act — race condition between check and act!
}
// ConcurrentHashMap provides atomic compound operations:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.putIfAbsent("counter", 1); // Atomic!
map.compute("counter", (k, v) -> (v == null) ? 1 : v + 1); // Atomic!4. Legacy API
// Hashtable has these legacy methods that don't exist in modern Map:
ht.elements(); // Returns Enumeration<V>
ht.keys(); // Returns Enumeration<K>
ht.contains(val); // Same as containsValue() — confusing naming!Migration Guide
// BEFORE (legacy code):
Hashtable<String, String> config = new Hashtable<>();
config.put("host", "localhost");
config.put("port", "8080");
Enumeration<String> keys = config.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
System.out.println(key + "=" + config.get(key));
}
// AFTER (modern code — single-threaded):
Map<String, String> config = new HashMap<>();
config.put("host", "localhost");
config.put("port", "8080");
config.forEach((key, value) -> System.out.println(key + "=" + value));
// AFTER (modern code — multi-threaded):
Map<String, String> config = new ConcurrentHashMap<>();
config.put("host", "localhost");
config.put("port", "8080");
config.forEach((key, value) -> System.out.println(key + "=" + value));Properties Class — Hashtable's Child
java.util.Properties extends Hashtable and is still used for configuration:
Properties props = new Properties();
// Load from file
try (InputStream is = new FileInputStream("config.properties")) {
props.load(is);
}
// Access
String host = props.getProperty("db.host", "localhost");
String port = props.getProperty("db.port", "3306");
// Save to file
try (OutputStream os = new FileOutputStream("config.properties")) {
props.store(os, "Database Configuration");
}Note: While Properties extends Hashtable, it should only be used with String keys and values.
Common Mistakes
1. Using Hashtable in New Code
// WRONG — never use Hashtable
Hashtable<String, Integer> map = new Hashtable<>();
// CORRECT — use HashMap or ConcurrentHashMap
Map<String, Integer> map = new HashMap<>(); // Single-threaded
Map<String, Integer> map = new ConcurrentHashMap<>(); // Multi-threaded2. Assuming Thread Safety for Compound Operations
// WRONG — race condition between contains and put
if (!ht.containsKey(key)) {
ht.put(key, value);
}
// CORRECT — use ConcurrentHashMap's atomic operations
concurrentMap.putIfAbsent(key, value);3. Using Hashtable for null-safety
// Some developers use Hashtable because it rejects nulls
// WRONG approach — better to use Objects.requireNonNull() with HashMap:
Map<String, String> map = new HashMap<>();
map.put(Objects.requireNonNull(key), Objects.requireNonNull(value));Interview Questions
Q1: What is the difference between Hashtable and HashMap?
A: Hashtable is synchronized, doesn't allow null keys/values, has 11 initial capacity, uses modulo for indexing, and is legacy. HashMap is unsynchronized, allows one null key, has 16 initial capacity, uses bitwise AND, and supports treeification.
Q2: Why is ConcurrentHashMap preferred over Hashtable?
A: ConcurrentHashMap uses fine-grained locking (per-bucket), allowing concurrent reads and writes to different segments. Hashtable locks the entire table, serializing all operations.
Q3: Does Hashtable use treeification for collision chains?
A: No. Only HashMap (Java 8+) converts chains to Red-Black trees. Hashtable chains remain as linked lists with O(n) worst-case.
Q4: Can Hashtable have null keys or values?
A: No. Both throw NullPointerException. This is unlike HashMap which allows one null key and multiple null values.
Q5: What is the initial capacity of Hashtable?
A: 11 (a prime number). It's not constrained to powers of 2 like HashMap.
Q6: Is Hashtable's iterator fail-fast?
A: The Iterator from entrySet().iterator() is fail-fast. The legacy Enumeration from keys()/elements() is NOT fail-fast.
Q8: Why doesn't Hashtable allow null keys or values?
A: Hashtable calls key.hashCode() directly without null check, so null key throws NullPointerException. For values, it explicitly checks if (value == null) throw new NullPointerException(). This was a design choice for early Java; HashMap (Java 1.2) relaxed this to allow one null key and multiple null values.
Q9: Is Hashtable truly thread-safe for all operations?
A: Each individual method is synchronized, but compound operations (check-then-act) are NOT atomic. For example, if (!table.containsKey(key)) table.put(key, value) has a race condition between the two calls. Use ConcurrentHashMap.putIfAbsent() for atomic compound operations.
Q10: What is the difference between Hashtable and ConcurrentHashMap?
A: Hashtable synchronizes every method (one lock for entire table). ConcurrentHashMap uses fine-grained locking (segment/node level) allowing concurrent reads and limited concurrent writes. ConcurrentHashMap is significantly faster under contention and provides atomic compound operations.
Q11: Why is Hashtable considered obsolete?
A: (1) Coarse-grained synchronization creates bottleneck, (2) ConcurrentHashMap provides better concurrency, (3) For single-threaded use HashMap is faster, (4) Hashtable doesn't support null keys/values, (5) Legacy Enumeration iteration vs modern Iterator, (6) Not part of modern Collections design philosophy.
Q12: Can Hashtable work with Java Streams?
A: Yes, since Java 8. Hashtable inherits from Dictionary but also implements Map interface, so hashtable.entrySet().stream() works. However, the synchronized nature adds overhead. For stream processing, HashMap or ConcurrentHashMap is preferred.
Summary
| Aspect | Hashtable |
|---|---|
| Status | Legacy — NEVER use in new code |
| Replace with | ConcurrentHashMap (concurrent) or HashMap (single-thread) |
| Null support | None (keys and values both rejected) |
| Thread-safety | Per-method synchronized (coarse-grained) |
| Performance | Poor under contention |
| Treeification | No |
| Only valid use | Maintaining legacy code; Properties class |
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Hashtable in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, collections, framework, map
Related Java Master Course Topics