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

HashMap vs Hashtable

Find out what actually separates the two beyond a capital letter, and why the older one is still in the language long after it stopped being advice.

6 min read Updated September 30, 2026 Last verified August 17, 2026
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.

Hashtable also has operations that express a compound update in one call: current Java provides synchronised putIfAbsent, computeIfAbsent, compute and merge. Use the operation whose contract covers your change. An arbitrary multi-call invariant still needs a consistently used outer lock, often the Hashtable instance itself so it coordinates with its method locks.

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.

Separate calls do not form a transaction#

Step through a lost increment

Two threads each want to add one to a counter starting at 0. Predict the final value, then compare separate calls with one atomic update per thread.

  1. Separate calls can run: A reads 0; B reads 0; A writes 1; B writes 1. Final value: 1.
  2. Atomic updates run as whole operations: A changes 0 to 1; B changes 1 to 2. Final value: 2.

One permitted interleaving loses an increment despite each individual call being protected. Enable JavaScript to step through it.

This is a fixed interleaving, not live Java threads or a performance benchmark. Atomic compute models a correctly used Hashtable or ConcurrentHashMap operation; a plain HashMap does not gain thread safety from the method name.

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.

A lost update need not throw. It can happen with only two callers, and a controlled interleaving can reproduce it: pause both threads after they read, then let each write its saved value plus one. Traffic changes the likelihood, not whether the race is possible. A single-thread test does not exercise that schedule.

This model applies beyond hashing. Check the operation’s documented atomicity and the invariant your caller needs; the foundations pillar asks the same question about other containers.

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 expressing it as one atomic method such as compute or merge — on Hashtable itself or on ConcurrentHashMap — 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 Usually ConcurrentHashMap Use an atomic method that covers the update
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
Situation
Single-threaded, or confined to one thread
Take
HashMap
Because
Locking you never use still costs
Situation
Genuinely shared and updated concurrently
Take
Usually ConcurrentHashMap
Because
Use an atomic method that covers the update
Situation
Maintaining old code that already uses it
Take
Hashtable
Because
Changing it buys nothing on its own
Situation
You need null as a key or a value
Take
HashMap
Because
Hashtable rejects both

IF YOU REMEMBER ONE THING

Hashtable protects its operations, including provided compound methods. Separately calling get and put leaves a gap; use a suitable atomic update or consistently lock the whole sequence. ConcurrentHashMap also requires correct operations, and a plain HashMap still needs protection when shared for concurrent mutation.

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.

The library Browse all articles