Java

HashMap vs Hashtable in Java: Key Differences

Compare HashMap and Hashtable in Java, including synchronization, null keys, performance, legacy status, and modern alternatives.

Side-by-side diagram comparing modern HashMap usage and legacy Hashtable behavior.

HashMap and Hashtable both store key-value pairs, but they belong to different eras of Java. Most modern Java code should start with HashMap, then choose a concurrent collection when thread safety is required.

Quick answer

Use HashMap for normal map usage. Use ConcurrentHashMap for shared concurrent access. Avoid new Hashtable usage unless you are maintaining old APIs or legacy code that already depends on it.

Hashtable is synchronized, rejects null, and is considered a legacy collection. HashMap is unsynchronized, allows one null key, and is the standard general-purpose map for modern Java code.

Quick comparison

FeatureHashMapHashtable
IntroducedJava 1.2 Collections FrameworkJava 1.0 legacy class
Thread safetyNot synchronizedSynchronized methods
Null keys and valuesAllows one null key and multiple null valuesDoes not allow null keys or values
Iterator behaviorFail-fast iteratorFail-fast iterator on collection views, legacy enumeration also exists
Typical useGeneral-purpose mapLegacy code compatibility
Modern alternative for concurrencyConcurrentHashMapUsually not recommended for new code

Basic usage

A HashMap stores values by key and is the default choice for request-scoped lookup tables, grouping logic, caches inside one method, and many everyday Java tasks.

Map<String, Integer> scores = new HashMap<>();
scores.put("alice", 42);
scores.put("bob", 37);

Integer aliceScore = scores.get("alice");

A Hashtable looks similar at first glance:

Hashtable<String, Integer> legacyScores = new Hashtable<>();
legacyScores.put("alice", 42);

The similar API is one reason the difference can be confusing. The runtime behavior and modern recommendation are not the same.

Synchronization

Hashtable synchronizes most public methods. That sounds convenient, but it often creates coarse-grained locking. Every read and write competes for the same monitor, which can become expensive under concurrent access.

HashMap does not synchronize access. If multiple threads mutate the same map, external synchronization is required. In modern code, ConcurrentHashMap is usually a better choice because it is designed for concurrent reads and updates.

Map<String, Integer> counts = new ConcurrentHashMap<>();
counts.merge("login", 1, Integer::sum);

This does not mean every map should be concurrent. If a map is local to one request, one method, or one thread, a plain HashMap is usually simpler and faster.

Null handling

HashMap allows one null key because keys must remain unique. It also allows multiple null values.

Map<String, String> map = new HashMap<>();
map.put(null, "default");
map.put("missing", null);

Hashtable rejects null keys and null values. This difference can matter when migrating legacy code because a value that was legal in a HashMap can throw a NullPointerException in a Hashtable.

In production code, allowing null values is not always a good idea. It can make map.get(key) ambiguous: did the key exist with a null value, or did the key not exist? Use containsKey when that distinction matters.

Performance and API design

For single-threaded or request-scoped data, HashMap is generally faster and easier to reason about. It is also the collection most Java developers expect to see.

For shared mutable state, do not switch to Hashtable just because it is synchronized. Prefer ConcurrentHashMap, immutable maps, or a design that avoids shared mutation.

A useful design question is: who owns this map? If one method builds it and returns a result, use HashMap. If multiple threads update it over time, use a concurrent data structure or redesign the ownership boundary.

What about Collections.synchronizedMap?

Java also provides Collections.synchronizedMap(new HashMap<>()). This wraps a map with synchronized access.

Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

This can be useful for compatibility, but it is not the same as ConcurrentHashMap. Iteration still requires external synchronization, and the single lock can still become a bottleneck. For most new concurrent code, ConcurrentHashMap is the stronger default.

Common mistakes

The first mistake is using Hashtable in new code because it sounds safer. Synchronized methods do not automatically make a larger workflow thread-safe.

The second mistake is sharing a mutable HashMap across threads without protection. This can produce race conditions, lost updates, and unpredictable behavior.

The third mistake is storing mutable values inside a concurrent map and assuming the value itself is protected. A concurrent map protects map operations; it does not make the object stored in the map immutable or thread-safe.

Recommendation

Use HashMap for normal map usage. Use ConcurrentHashMap for concurrent workloads. Treat Hashtable as a legacy type that you may need to read, maintain, or migrate away from.

Read Java equals and hashCode Explained for key correctness and HashMap Collision Handling Explained for bucket behavior. Read ConcurrentHashMap Explained for the modern concurrent alternative and HashMap Load Factor Explained for capacity and resizing. Use Time Complexity of Java Collections and the Big-O Cheat Sheet when comparing map operations. For the guided sequence, browse the Java Collections Learning Path.