Skip to the content
Software Made Clear Diagrams that show the mechanism About

HashMap vs Hashtable

ANSWER

In Java the practical differences are locking and nulls: Hashtable synchronises every method and rejects null, HashMap does neither. The older class stays for compatibility, and the locking it gives you is rarely the locking you wanted.

IN PLAIN TERMS

One filing cabinet has a lock every visitor must turn, even when the building is empty. The other is built the same way without it. Turning the lock is not free, and it does not protect the thing most people assume it protects.

In Java, HashMap and Hashtable turn up together in an unfamiliar codebase, or in an interview question that expects a quick answer. Both are hash tables in the general sense — an array of buckets addressed by a key’s hash code — so the structure underneath is not what separates them. What separates them is what each one guarantees around that structure, and the part worth understanding is not “one is synchronised and one isn’t” but what that synchronisation does and does not buy the code calling it.

What actually differs#

AspectHashMapHashtable
SynchronisationNone — no method takes a lock.Synchronised on the instance; its operations take the class’s own lock.
Null handlingPermits null values and one null key.Rejects null outright, as a key or a value; passing either throws.
Iteration while modifiedIts iterator is fail-fast, on a best-effort basis, and throws when it notices a structural change.Its Enumeration is not fail-fast at all; its separate Iterator is, on the same best-effort basis as HashMap’s.
StandingThe ordinary choice for new code.Predates the Collections Framework; kept so old code keeps compiling.

The row that trips people up is null. It is not “HashMap allows null and Hashtable does not” stated loosely — HashMap’s own documentation is specific: it permits null values and the null key, singular, because a map can hold only one entry per distinct key and null is allowed to be that key like any other. Nothing caps how many entries may hold a null value, since values carry no uniqueness rule the way keys do. Hashtable draws no such line at all — its put documents that neither argument may be null, and passing either throws a NullPointerException immediately, before anything is stored.

Iteration during modification is the other row worth reading carefully rather than assuming. HashMap’s collection-view iterators are fail-fast: a structural change made any way other than through that same iterator’s own remove is meant to throw a ConcurrentModificationException on the next step. Hashtable’s Iterator behaves the same way, on the same terms. Its older Enumeration — returned by keys() and elements(), predating the Iterator interface entirely — does not: Hashtable’s own documentation says a structural change during enumeration leaves the results undefined, not exception-throwing. And even the fail-fast promise both iterators do make is not a promise at all in the strict sense — the documentation for both classes says explicitly that fail-fast behaviour cannot be guaranteed in the presence of unsynchronised concurrent modification, and that the exception is thrown on a best-effort basis meant to catch bugs, not to be relied on for correctness.

Why per-method locking is the wrong lock#

Synchronising every method makes each individual call atomic. It does nothing for a sequence of calls, and most of the operations that actually matter to a caller are sequences. Checking whether a key is already present and then inserting it if not is two separate calls to a synchronised map — the check finishes, releases whatever lock it held, and only then does the insert begin. Nothing stops a second thread from running its own check in the gap between them, seeing the same absence, and proceeding to insert as well. Each individual call was exactly as safe as advertised; the sequence built from two of them was never protected at all.

That is the sense in which Hashtable’s thread safety does not match what most callers actually need. A caller who needs the check and the insert to happen as one unit has to hold a lock across both calls, from outside the class, for the whole sequence — at which point Hashtable’s own per-method lock is doing nothing useful. It still runs on every call, still costs whatever a lock costs to take and release, and still buys nothing beyond what the caller’s own outer lock already provides.

The habit that solves this is the same one worth applying to any standard-library container before trusting what its name implies: a Python deque’s own documented behaviour is a different structure entirely, but the same rule holds there too, where a keyword argument discards data silently rather than raising the exception its name would suggest. Read the actual contract, not the shape a class merely resembles. Java’s own class built for this problem is ConcurrentHashMap, and it documents specific methods — putIfAbsent, computeIfAbsent, merge among them — as atomic for the entire call, which is exactly what solves the check-then-insert problem above, provided the check-then-insert is expressed as one of those calls rather than as two separate ones. What it does not document is atomicity across arbitrary separate calls: a plain get followed by a plain put on a ConcurrentHashMap can still race exactly the way it can on a Hashtable, because that sequence was never one of the operations the class promises to make atomic.

Synchronised is not thread-safe#

The mistake is reading “synchronised” as “safe to share” without asking safe for what. The shape: a Hashtable handed to several threads, every call to it synchronised exactly as documented, and a compound operation — check whether a key is absent, then insert it, or read a counter and write back its incremented value — spread across separate calls rather than expressed as one. Two threads interleave between the check and the insert, or between the read and the write, and one thread’s update quietly overwrites the other’s.

Nothing throws. No test written against a single thread catches it, because a single thread never interleaves with itself. The loss is proportional to how often two threads land in that gap at the same time, which means it is invisible at low traffic and shows up only once enough concurrent calls make the gap likely to be hit. The tell is a total or a count that comes out slightly low, inconsistently, and never in a way that reproduces on demand.

None of this is specific to hashing, even though hashing is exactly where it happened here. Checking what a structure actually documents, instead of trusting what its name promises, is the standing question this site’s foundations pillar keeps putting to trees, graphs and hash tables in turn.

Hashtable is not at fault here, and its documentation never promised what the mistake assumed. The guarantee is per call — each method finishes as an indivisible step — and it was read as per operation, as though the caller’s whole sequence of calls came wrapped in the same protection. Fixing this means locking around the sequence explicitly, or reaching for a class whose atomic methods actually cover that sequence, not tuning or replacing a class that was already doing exactly what it said it would.

Situation Take Because
Single-threaded, or confined to one thread HashMap Locking you never use still costs
Genuinely shared and updated concurrently Neither Per-method locking is the wrong shape
Maintaining old code that already uses it Hashtable Changing it buys nothing on its own
You need null as a key or a value HashMap Hashtable rejects both

IF YOU REMEMBER ONE THING

Hashtable’s guarantee is per call, not per operation, and the sequence you actually care about — check, then insert — was never covered by it. Read what a class documents rather than what its name implies; the gap between the two is where the bug lives.

Questions people also ask

5 QUESTIONS
Is Hashtable deprecated?

No. It carries no deprecation warning and still compiles and runs like any other class. Java's own package documentation groups it among the legacy collection classes, and its class page recommends HashMap or ConcurrentHashMap for new code, but nothing in the language stops existing code from continuing to use it.

Can HashMap hold null keys?

Yes, one — a map can only ever hold one entry per distinct key, and null is allowed to be that key, so it caps out the same way any other key would. Values have no such uniqueness rule, so any number of entries may hold a null value at once.

What should I use for a map shared between threads?

ConcurrentHashMap, generally — its locking is scoped to parts of the table rather than the whole thing, and specific methods such as putIfAbsent and computeIfAbsent are documented as atomic. That guarantee stops at the method call: a plain get followed by a separate put can still race unless the atomic method is the one actually used.

Is a hash map the same as a hash table?

Hash table is the general idea: an array of buckets addressed by a key's hash code, the same underneath in any language that has one. HashMap and Hashtable are both Java's implementations of that idea, differing only in the guarantees layered on top — the concept is shared; the guarantees are not.

Why is Hashtable still in the language?

Removing a public class would break every program still importing it, and Java's compatibility commitment does not allow that. The class page now points new code toward HashMap or ConcurrentHashMap instead, but Hashtable predates the Collections Framework it was later retrofitted into, and code already built on it still has to keep compiling.