W3docs

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:

  1. Backwards compatibility. A handful of standard-library classes return HashtableSystem.getProperties() returns a Properties instance, which extends Hashtable<Object, Object>. Some old JNDI APIs (InitialContext(Hashtable)) take one as an argument.
  2. Existing code. Any codebase older than about 2005 may still have Hashtable in places where nobody felt like migrating.
  3. 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:

FeatureHashtableHashMap
Thread safetyevery method synchronized on the whole tablenot thread-safe
null keyrejected (NullPointerException)one allowed
null valuerejected (NullPointerException)many allowed
Iteration orderunspecifiedunspecified
Default capacity1116
Capacity growth2*old + 1 (odd-numbered sizes, slower modulo)doubles to next power of two
Pre-collections APIelements(), keys() enumerationsnone
Java 8 treeificationnoyes — buckets become trees past 8 entries
Fail-fast iteratorsyes, since 1.2 retrofityes
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 MapHashMap. Stop. The synchronized overhead of Hashtable is pure cost with no benefit.
  • Multi-threaded code, you want a MapConcurrentHashMap. 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 elseCollections.synchronizedMap(new HashMap<>()). Same single-lock behaviour as Hashtable but composes with the modern collections API. Still worse than ConcurrentHashMap if you can use that.
  • You're seeing an API that requires Hashtable (Properties, JNDI) → use the Hashtable because 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.

java— editable, runs on the server

What to take from the run:

  • The basic API is Map's, so Hashtable looks like HashMap for simple use. Identical results, slower.
  • Nulls are rejected on both sides — that's the only Map in the JDK family that rejects null values as well as null keys.
  • The Hashtable counter is wrong. Each method is synchronized, but get then put are two separate atomic operations, not one. Threads race in between and lose updates.
  • The ConcurrentHashMap version with merge is 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

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?