Java Hashtable
The legacy synchronized Hashtable in Java, why it's superseded by HashMap and ConcurrentHashMap, and when it appears.
Java Hashtable
Hashtable<K, V> is the original hash-based map in Java, dating back to JDK 1.0 in 1996 — two years before the collections framework was added. When Map, HashMap, and the rest landed in JDK 1.2, Hashtable was retrofitted to implement Map, but its quirks remained: every method is synchronized, both keys and values reject null, and the public API includes pre-collections methods (elements(), keys()) that predate Iterator.
In new code you almost never want it. This chapter is here so you recognise the class when you see it, understand why it's still around, and know what to use instead.
Why it still exists
Three reasons:
- Backwards compatibility. A handful of standard-library classes return
Hashtable—System.getProperties()returns aPropertiesinstance, which extendsHashtable<Object, Object>. Some old JNDI APIs (InitialContext(Hashtable)) take one as an argument. - Existing code. Any codebase older than about 2005 may still have
Hashtablein places where nobody felt like migrating. - Misplaced familiarity. It comes up in interviews and tutorials, and beginners sometimes reach for it because "I want a thread-safe map" — without knowing about
ConcurrentHashMap.
How it differs from HashMap
Hashtable and HashMap are both hash tables with chaining, both implement Map<K, V>, and both are O(1) expected for get/put/remove. The differences:
| Feature | Hashtable | HashMap |
|---|---|---|
| Thread safety | every method synchronized on the whole table | not thread-safe |
null key | rejected (NullPointerException) | one allowed |
null value | rejected (NullPointerException) | many allowed |
| Iteration order | unspecified | unspecified |
| Default capacity | 11 | 16 |
| Capacity growth | 2*old + 1 (odd-numbered sizes, slower modulo) | doubles to next power of two |
| Pre-collections API | elements(), keys() enumerations | none |
| Java 8 treeification | no | yes — buckets become trees past 8 entries |
| Fail-fast iterators | yes, since 1.2 retrofit | yes |
clone() | yes (shallow) | yes (shallow) |
The synchronization is the headline difference and the biggest reason Hashtable is slow: every read and every write acquires the same lock on the entire table. A multi-threaded program with two threads doing nothing but get against a Hashtable is serialised — they take turns on the lock.
Why synchronized on every method isn't real thread safety
A surprisingly common bug: developers see "every method is synchronized" and think Hashtable makes their multi-threaded code correct. It doesn't. Compound operations are still racy:
if (!table.containsKey(key)) { // synchronized
table.put(key, computeValue()); // synchronized — but separate lock acquisition
}Between the two calls, another thread can put the same key. Both threads see containsKey return false, both compute, both put. You get two evaluations and the wrong value wins.
The fix in 2026 is not to fix Hashtable but to use ConcurrentHashMap, which has atomic compound operations baked in: putIfAbsent, computeIfAbsent, merge, replace(k, old, new). They acquire the right locks internally and they do the test-and-set as one operation.
What to use instead
The decision flow when you're tempted to write new Hashtable<>():
- Single-threaded code, you want a
Map→HashMap. Stop. Thesynchronizedoverhead ofHashtableis pure cost with no benefit. - Multi-threaded code, you want a
Map→ConcurrentHashMap. Lock-striped (lock-free for reads in modern JDKs), no global lock, atomic compound operations, far better scalability. - Multi-threaded code, you genuinely need every operation atomic with everything else →
Collections.synchronizedMap(new HashMap<>()). Same single-lock behaviour asHashtablebut composes with the modern collections API. Still worse thanConcurrentHashMapif you can use that. - You're seeing an API that requires
Hashtable(Properties, JNDI) → use theHashtablebecause the API requires it; don't introduce a parallel one.
The pre-collections quirks
Hashtable predates Iterator and exposes Enumeration<K> instead:
Enumeration<String> keys = table.keys();
while (keys.hasMoreElements()) {
System.out.println(keys.nextElement());
}Enumeration has only hasMoreElements() and nextElement() — no remove(). The retrofit in 1.2 added keySet(), entrySet(), and values() from Map, and you can iterate those with a normal Iterator. But because both APIs exist on the same object, you'll see both styles in the wild. Prefer the Map view; it's the language you already know.
A worked example: Hashtable, why it rejects nulls, and the race it doesn't protect against
The program below demonstrates the visible differences from HashMap — the synchronized methods, the rejected nulls, the legacy enumeration — and shows the check-then-act race that Hashtable's synchronization does not solve.
What to take from the run:
- The basic API is
Map's, soHashtablelooks likeHashMapfor simple use. Identical results, slower. - Nulls are rejected on both sides — that's the only
Mapin the JDK family that rejects null values as well as null keys. - The
Hashtablecounter is wrong. Each method is synchronized, butgetthenputare two separate atomic operations, not one. Threads race in between and lose updates. - The
ConcurrentHashMapversion withmergeis correct and fast. That's the right tool for "thread-safe map" in 2026.
What's next
Hashtable has one descendant you'll actually use: Properties, the configuration container behind System.getProperties() and the .properties file format. It's narrow in scope and pleasant to use; it's the next chapter, and the last "data structure" chapter in this part of the book before we move on to iteration, sorting, and the static utility methods.
Practice
Your senior engineer asks you to remove a `Hashtable<String, Integer>` from a multi-threaded counter that does `int n = t.get(k); t.put(k, n + 1);`. What's the right replacement?