Hashtable in Java – Complete Guide with Examples
Hashtable is a key–value hash table that has been in Java since JDK 1.0, before the Collections Framework existed. Like HashMap, it maps unique keys to values, but every public method is synchronized, and it does not allow null keys or values.
Hashtable was later updated to implement the Map interface. You will meet it in older code, in the Properties class, and in interviews. This tutorial explains how it works, how it differs from HashMap, and what to use instead today.
1. What Is a Hashtable?
- Key–value storage with unique keys, like every Map.
- Synchronized: only one thread at a time can call any of its methods.
- No
nullkeys or values; trying throwsNullPointerException. - No guaranteed order of entries.
- Legacy API: extends the old abstract class
Dictionaryand supportsEnumeration. - Default capacity 11, load factor 0.75; grows to
2 × old + 1.
2. Class Hierarchy and Declaration
public class Hashtable<K, V> extends Dictionary<K, V>
implements Map<K, V>, Cloneable, java.io.Serializable
java.util.Properties extends Hashtable.
3. Your First Hashtable Program
Because Hashtable implements Map, everyday use looks exactly like HashMap:
import java.util.Hashtable;
import java.util.Map;
public class HashtableQuickStart {
public static void main(String[] args) {
Map<Integer, String> rooms = new Hashtable<>();
rooms.put(101, "Asha");
rooms.put(102, "Ravi");
rooms.put(103, "Meera");
System.out.println("Rooms: " + rooms);
System.out.println("get(102): " + rooms.get(102));
System.out.println("has room 103: " + rooms.containsKey(103));
rooms.put(102, "Kiran"); // replace the guest
rooms.remove(101);
System.out.println("After update: " + rooms + ", size=" + rooms.size());
}
}
Rooms: {103=Meera, 102=Ravi, 101=Asha}
get(102): Ravi
has room 103: true
After update: {103=Meera, 102=Kiran}, size=2
4. Constructors
| Constructor | What it does |
|---|---|
Hashtable() | Empty table, capacity 11, load factor 0.75. |
Hashtable(int initialCapacity) | Empty table with the given capacity. |
Hashtable(int initialCapacity, float loadFactor) | Empty table with the given capacity and load factor. |
Hashtable(Map<? extends K, ? extends V> t) | A table with the same entries as t. |
5. Why Hashtable Rejects null
Hashtable calls key.hashCode() directly, which fails for a null key, and it checks values explicitly. There is also a design reason: in a shared, synchronized map, get() returning null must clearly mean "no entry", not "entry with a null value".
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class HashtableNulls {
static void tryPut(String label, Map<String, String> map, String k, String v) {
try {
map.put(k, v);
System.out.println(label + " put(" + k + ", " + v + ") -> ok");
} catch (NullPointerException e) {
System.out.println(label + " put(" + k + ", " + v + ") -> NullPointerException");
}
}
public static void main(String[] args) {
tryPut("HashMap: ", new HashMap<>(), null, "x");
tryPut("HashMap: ", new HashMap<>(), "k", null);
tryPut("Hashtable:", new Hashtable<>(), null, "x");
tryPut("Hashtable:", new Hashtable<>(), "k", null);
}
}
HashMap: put(null, x) -> ok HashMap: put(k, null) -> ok Hashtable: put(null, x) -> NullPointerException Hashtable: put(k, null) -> NullPointerException
6. Legacy Methods and Enumeration
Hashtable keeps its JDK 1.0 methods: keys() and elements() return an Enumeration, and contains(value) checks values, not keys, which is a common source of confusion.
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.TreeMap;
public class HashtableEnumeration {
public static void main(String[] args) {
Hashtable<String, Integer> ages = new Hashtable<>();
ages.put("Asha", 31);
ages.put("Ravi", 27);
ages.put("Meera", 35);
// Legacy style: Enumeration over keys and over values
Enumeration<String> keys = ages.keys();
int count = 0;
while (keys.hasMoreElements()) {
keys.nextElement();
count++;
}
System.out.println("Keys counted with keys(): " + count);
int total = 0;
Enumeration<Integer> values = ages.elements();
while (values.hasMoreElements()) total += values.nextElement();
System.out.println("Sum of ages with elements(): " + total);
// Legacy contains() checks VALUES (same as containsValue)
System.out.println("contains(27): " + ages.contains(27));
// Modern style works too, because Hashtable is a Map
ages.forEach((name, age) -> { if (age > 30) System.out.println(name + " is over 30"); });
System.out.println("Sorted: " + new TreeMap<>(ages));
}
}
Keys counted with keys(): 3
Sum of ages with elements(): 93
contains(27): true
Meera is over 30
Asha is over 30
Sorted: {Asha=31, Meera=35, Ravi=27}
| Legacy method | Modern Map equivalent |
|---|---|
keys() | keySet().iterator() |
elements() | values().iterator() |
contains(value) | containsValue(value) |
rehash() (protected) | Automatic resizing |
7. Thread Safety in Hashtable
Each method, including merge() and compute(), is synchronized on the Hashtable object. Two threads counting clicks therefore get the right total:
import java.util.Hashtable;
import java.util.Map;
public class HashtableThreads {
public static void main(String[] args) throws InterruptedException {
Map<String, Integer> counter = new Hashtable<>();
Runnable task = () -> {
for (int i = 0; i < 50_000; i++) {
counter.merge("clicks", 1, Integer::sum); // merge is synchronized
}
};
Thread t1 = new Thread(task), t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Total clicks: " + counter.get("clicks"));
}
}
Total clicks: 100000
The downsides:
- One lock for everything: while one thread reads, all others wait, even for different keys.
- Compound actions still need care:
if (!t.containsKey(k)) t.put(k, v);is two separate calls; useputIfAbsent()or synchronize on the table. - Iteration needs a manual lock: hold
synchronized (table)for the whole loop.
8. How Hashtable Works Internally
Hashtable is an array of buckets, each holding a linked list of entries. For a key, it calculates index = (hash & 0x7FFFFFFF) % table.length, then searches that bucket with equals(). Differences from modern HashMap:
- It uses modulo with an odd table size (11, 23, 47 …) instead of a power of two.
- It does not spread the hash bits.
- Buckets never turn into trees, so many collisions mean O(n) lookups.
- The table is created immediately, not lazily.
9. Properties: Hashtable's Best-Known Subclass
Properties stores configuration as string key–value pairs and can load them from .properties files. It extends Hashtable, which is why it is thread-safe.
import java.io.IOException;
import java.io.StringReader;
import java.util.Properties;
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
String config = """
app.name=Shop
app.port=8080
# comments are ignored
""";
Properties props = new Properties(); // Properties extends Hashtable<Object, Object>
props.load(new StringReader(config));
System.out.println("Name: " + props.getProperty("app.name"));
System.out.println("Port: " + Integer.parseInt(props.getProperty("app.port")));
System.out.println("Timeout: " + props.getProperty("app.timeout", "30s") + " (default)");
}
}
Name: Shop Port: 8080 Timeout: 30s (default)
Use getProperty() and setProperty() rather than get() and put(), so that only strings are stored.
10. Common Hashtable Methods
| Method | Description |
|---|---|
put(k, v), get(k), remove(k) | Basic map operations (synchronized). |
containsKey(k), containsValue(v), contains(v) | Check for a key or a value. |
keys(), elements() | Legacy enumerations of keys and values. |
keySet(), values(), entrySet() | Synchronized collection views. |
putIfAbsent, merge, compute, getOrDefault | Java 8 methods, also synchronized. |
size(), isEmpty(), clear() | Count, check and empty. |
clone() | Shallow copy. |
11. Hashtable vs HashMap vs ConcurrentHashMap
| Feature | Hashtable | HashMap | ConcurrentHashMap |
|---|---|---|---|
| Introduced | JDK 1.0 | JDK 1.2 | Java 5 |
| Thread-safe | Yes, one lock | No | Yes, fine-grained |
| null key / values | No / No | One / Yes | No / No |
| Default capacity | 11 | 16 | 16 |
| Growth | 2n + 1 | 2n | 2n |
| Iteration | Enumeration and fail-fast Iterator | Fail-fast Iterator | Weakly consistent Iterator |
| Parent class | Dictionary | AbstractMap | AbstractMap |
12. What to Use Instead
- Single-threaded code:
HashMap. - Shared between threads:
ConcurrentHashMap, which scales much better. - Need a synchronized wrapper around any map:
Collections.synchronizedMap(). - Configuration:
Propertiesis still fine.
Hashtable is not deprecated, but the JDK documentation itself recommends HashMap or ConcurrentHashMap for new code.
13. Summary
- Hashtable is a legacy, synchronized hash table that implements
Map. - It does not allow
nullkeys or values. - Default capacity is 11, and it grows to 2n + 1.
- It supports old
Enumerationmethods;contains()checks values. - Properties extends it; for new code use HashMap or ConcurrentHashMap.
Related Tutorials
Reference: Java SE 21 API: java.util.Hashtable
Frequently Asked Questions (FAQ)
Q1. What is a Hashtable in Java?
Hashtable is a legacy class in java.util that stores key–value pairs in a hash table. All its methods are synchronized, it does not allow null keys or values, and it implements the Map interface.
Q2. Why does Hashtable not allow null keys or values?
It calls hashCode() on the key directly and explicitly rejects null values. Forbidding null also makes get() unambiguous in multi-threaded code: null always means "no entry".
Q3. What is the difference between HashMap and Hashtable?
Hashtable is synchronized, legacy (JDK 1.0), forbids null, starts at capacity 11 and supports Enumeration. HashMap is unsynchronized and faster, allows one null key and null values, starts at 16, and uses tree buckets for heavy collisions.
Q4. Is Hashtable thread-safe?
Yes, each method is synchronized. But sequences of calls are not atomic, and iteration requires holding the table's lock manually.
Q5. Why is ConcurrentHashMap preferred over Hashtable?
Hashtable uses one lock for the whole table, so threads block each other even when working on different keys. ConcurrentHashMap lets reads run without locking and locks only small parts of the table for writes, so it scales much better, and its iterators never throw ConcurrentModificationException.
Q6. What is the default capacity of a Hashtable?
11, with a load factor of 0.75. When it exceeds the threshold, the new capacity is old × 2 + 1 (11, 23, 47 …).
Q7. Is Hashtable deprecated?
No. It is still part of the JDK and fully supported, but it is considered a legacy class. The documentation recommends HashMap for single-threaded use and ConcurrentHashMap for concurrent use.
Q8. What does Hashtable.contains() check?
Values, not keys. contains(v) is the same as containsValue(v). Use containsKey(k) to check for a key.
Q9. What is the difference between keys() and keySet()?
keys() is the legacy method returning an Enumeration. keySet() returns a Set view that supports iterators, streams and removal.
Q10. Is the Hashtable iterator fail-fast?
The iterators from keySet(), values() and entrySet() are fail-fast. The Enumeration objects from keys() and elements() are not.
Q11. What is the Dictionary class?
An abstract class from JDK 1.0 that defined key–value storage before the Map interface existed. Hashtable extends it. It is obsolete; new classes implement Map instead.
Q12. Does Hashtable maintain insertion order?
No. Entries are returned in an order based on their buckets. Use LinkedHashMap (wrapped with Collections.synchronizedMap() if needed) for insertion order.
Q13. What is the time complexity of Hashtable operations?
get, put and remove are O(1) on average. Because Hashtable buckets never become trees, many collisions in one bucket can degrade lookups to O(n).
Q14. Why does Properties extend Hashtable?
Properties was created in JDK 1.0, when Hashtable was the only general hash table. That inheritance makes Properties thread-safe, but it also exposes put(Object, Object), so you should use setProperty() to keep it string-only.
Q15. How do I iterate over a Hashtable safely while other threads use it?
synchronized (table) {
for (Map.Entry<String, Integer> e : table.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
}
Or switch to ConcurrentHashMap, whose iterators are safe without locking.
Q16. Can I convert a Hashtable to a HashMap?
Yes, pass it to the constructor: Map<K, V> map = new HashMap<>(hashtable);. Since a Hashtable has no null entries, every entry copies over cleanly.
Q17. Are Hashtable's Java 8 methods like merge() synchronized?
Yes. Hashtable overrides merge(), compute(), computeIfAbsent(), putIfAbsent() and the others as synchronized methods, so each call is atomic.
Q18. How does Hashtable calculate the bucket index?
It uses (hash & 0x7FFFFFFF) % table.length. The mask removes the sign bit so the result is never negative.
Q19. What happens if I put a duplicate key in a Hashtable?
The new value replaces the old one, and put() returns the old value, exactly as in HashMap.
Q20. When might I still use Hashtable?
When maintaining old code or working with APIs that require it (for example some JNDI and older library methods), and indirectly through Properties. For new code, choose HashMap or ConcurrentHashMap.