Back to Blog
Java

Java HashMap vs Hashtable: Key Differences

java hashmap vs hashtable: Understand the practical differences between HashMap and Hashtable in Java: synchronization, null handling, performance, and when to choose...

HashMapHashtableConcurrencyCollectionsNull Handling
A visual comparison between Java HashMap and Hashtable showing key differences in synchronization and null handling.

java hashmap vs hashtable requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When working with key-value pairs in Java, you'll eventually face the choice between HashMap and Hashtable. While both implement the Map interface, they differ significantly in synchronization, null handling, and performance. This article explains those differences in practical terms so you can decide which one fits your use case.

Synchronization and Thread Safety

The most fundamental difference is that Hashtable is synchronized, meaning its methods are guarded by a lock on the entire table. This makes Hashtable thread-safe for basic operations, but at a cost: every read and write acquires the same lock, which serializes access. In a multi-threaded application, this can become a bottleneck if the map is heavily accessed.

HashMap, on the other hand, is not synchronized. It offers no thread safety guarantees. If multiple threads access a HashMap concurrently and at least one thread modifies it structurally, you must synchronize externally. Structural modification includes adding or removing entries, but not updating an existing value.

Consider this example where a shared HashMap is used without external synchronization:

Map<String, Integer> map = new HashMap<>(); // Thread A map.put("key", 42); // Thread B Integer value = map.get("key");

This may produce unexpected results because the internal array and linked list structures can be corrupted during concurrent modification. To make a HashMap thread-safe, you can wrap it using Collections.synchronizedMap, as shown below:

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

This wrapper synchronizes every method call, but compound operations like "check then put" still require manual synchronization. For many concurrent scenarios, ConcurrentHashMap is a better choice because it uses finer-grained locking and provides better throughput.

Null Keys and Values

Hashtable does not allow null as a key or a value. Attempting to insert a null key or value throws NullPointerException. This restriction is a legacy from the original Java 1.0 collections.

HashMap, in contrast, allows one null key and any number of null values. This is convenient when you need to represent absent values or use a null sentinel.

Map<String, String> map = new HashMap<>(); map.put(null, "value"); // allowed map.put("key", null); // allowed

If your code relies on null support, HashMap is the only choice between the two. This difference becomes critical when you are migrating legacy code that uses Hashtable — you must handle nulls explicitly before switching.

Performance Characteristics

Because Hashtable synchronizes each method, it incurs overhead from acquiring and releasing locks on every operation. In single-threaded code, this overhead is purely wasteful. HashMap has no such locking, so it generally provides better performance for single-threaded access.

Even in multi-threaded environments, Hashtable is often outperformed by ConcurrentHashMap, which uses lock striping to allow concurrent reads and limited concurrent writes. Therefore, Hashtable is rarely the best performance choice.

It's worth noting that both HashMap and Hashtable rely on the hashCode() and equals() methods of keys. If those methods are not implemented correctly, both structures may degrade to linear-time lookups. The performance difference between the two is primarily due to synchronization, not the underlying data structure.

Iteration Behavior

HashMap uses a fail-fast iterator that throws ConcurrentModificationException if the map is structurally modified during iteration. This is a safety measure but can be surprising to developers who modify a map while iterating over it.

Hashtable also provides a fail-fast iterator in modern Java, but the older Enumeration interface it additionally supports does not have this behavior. If you use Hashtable's keys() or elements() methods, you get an Enumeration that may silently produce inconsistent results if the map is modified.

For most code, using the Map interface's entrySet() or forEach is recommended regardless of the implementation.

Legacy API and Naming

The naming convention is a clue: Hashtable begins with a lowercase letter, reflecting its age. It was part of Java 1.0, before the collections framework was introduced in Java 1.2. HashMap was added in Java 1.2 as part of the modern Collection framework.

You should avoid using classes from the legacy java.util classes like Hashtable unless you are maintaining older code. Modern Java code should use HashMap for single-threaded contexts and ConcurrentHashMap for multi-threaded contexts.

Decision Guidance

The choice depends on your concurrency requirements:

  • If your map is only accessed by one thread, use HashMap for simplicity and performance.
  • If you need a thread-safe map with good concurrency, use ConcurrentHashMap instead of Hashtable.
  • If you are stuck with a legacy API that expects Hashtable, you may have to use it, but you can often adapt with Collections.synchronizedMap wrappers.

Here is a quick comparison table to summarize:

FeatureHashMapHashtable
SynchronizationNoYes (whole map lock)
Null keyAllowed (one)Not allowed
Null valueAllowedNot allowed
Introduced inJava 1.2Java 1.0
Recommended forSingle-threaded codeLegacy code only

Impact of Incorrect Use in Production

Using Hashtable in a highly concurrent system can create log-contention issues that are hard to diagnose. The lock on the entire table means that even unrelated key reads are serialized, leading to unexpected latency under load. In contrast, ConcurrentHashMap allows multiple readers and limits lock contention to specific segments.

Another production concern is null handling. If a Hashtable is used to store values that might be null, the application will crash with NullPointerException at an unpredictable point. This often surfaces during data processing when a blank field is encountered.

Before adopting a map implementation, you should also consider the equals() and hashCode() implementations of your keys. If keys are mutable and their hash code changes after insertion, the map may fail to locate them, causing memory leaks that are visible only in long-running services.

Migrating Legacy Hashtable Usage

When migrating code from Hashtable to HashMap or ConcurrentHashMap, pay attention to the following:

  • Replace all Hashtable type references with the new interface type.
  • Check for reliance on null keys or values and validate input data.
  • Ensure iteration code uses the modern Map API rather than Enumeration.
  • If thread safety is required, choose ConcurrentHashMap and adjust any assumptions about iteration order.

A simple migration might look like this:

// Before Hashtable<String, Integer> table = new Hashtable<>(); table.put("a", 1); // After Map<String, Integer> map = new HashMap<>(table);

The copy constructor is convenient, but if you are replacing a synchronized structure with a non-synchronized one, you must verify that no other threads are relying on implicit synchronization.

When Hashtable Still Makes Sense

Despite its drawbacks, Hashtable remains in the JDK for backward compatibility. If you are maintaining a library that must run on very old Java versions or must interoperate with legacy code that expects a Hashtable, you might have to use it internally. In that case, document why you are using it and avoid exposing it in public APIs.

For new development, none of these reasons justify choosing Hashtable over HashMap or ConcurrentHashMap. The performance and flexibility advantages of modern maps are too significant.

java hashmap vs hashtable: Practical Usage and Code Examples | RYUSLOG DEV