Skip to content
Java collections 6 min read

Hashtable

Hashtable is one of Java’s original collection classes — it has been around since Java 1.0. It stores key-value pairs using a hash table internally and is fully synchronized, making every method thread-safe out of the box.

What is Hashtable?

Hashtable<K, V> lives in java.util and implements the Map interface (since Java 2, when the Collections Framework was introduced). Like HashMap, it maps unique keys to values using hashing — but with two important differences: all methods are synchronized, and neither keys nor values can be null.

import java.util.Hashtable;

public class BasicHashtable {
    public static void main(String[] args) {
        Hashtable<String, Integer> scores = new Hashtable<>();
        scores.put("Alice", 95);
        scores.put("Bob",   87);
        scores.put("Carol", 92);

        System.out.println(scores.get("Bob"));
        System.out.println(scores);
    }
}

Output:

87
{Carol=92, Alice=95, Bob=87}

Note: Hashtable does not guarantee any particular iteration order. The output order you see depends on the hash codes of the keys.

Creating a Hashtable

import java.util.Hashtable;

public class CreateHashtable {
    public static void main(String[] args) {
        // 1. Default constructor (initial capacity 11, load factor 0.75)
        Hashtable<String, Integer> ht1 = new Hashtable<>();

        // 2. Specify initial capacity
        Hashtable<String, Integer> ht2 = new Hashtable<>(20);

        // 3. Specify both initial capacity and load factor
        Hashtable<String, Integer> ht3 = new Hashtable<>(20, 0.5f);
    }
}

The initial capacity controls how many buckets are created upfront. The load factor (default 0.75) determines when the table rehashes — at 75% fullness by default.

Tip: The default initial capacity for Hashtable is 11 (prime), while HashMap uses 16 (power of two). This is a deliberate historical difference.

Common Methods

MethodDescription
put(K key, V value)Inserts or replaces a key-value pair
get(Object key)Returns the value for a key, or null if not found
remove(Object key)Removes and returns the value for a key
containsKey(Object key)Returns true if the key exists
containsValue(Object value)Returns true if the value exists
size()Number of key-value pairs
isEmpty()Returns true if the table is empty
keySet()Returns a Set of all keys
values()Returns a Collection of all values
entrySet()Returns a Set of all Map.Entry pairs
clear()Removes all mappings
keys()Returns an Enumeration of keys (legacy)
elements()Returns an Enumeration of values (legacy)
import java.util.Hashtable;
import java.util.Enumeration;

public class HashtableMethods {
    public static void main(String[] args) {
        Hashtable<String, String> capitals = new Hashtable<>();
        capitals.put("India",  "New Delhi");
        capitals.put("France", "Paris");
        capitals.put("Japan",  "Tokyo");

        // Check and retrieve
        System.out.println(capitals.containsKey("France"));    // true
        System.out.println(capitals.containsValue("Berlin"));  // false
        System.out.println(capitals.size());                   // 3

        // Legacy Enumeration iteration
        Enumeration<String> keys = capitals.keys();
        while (keys.hasMoreElements()) {
            String k = keys.nextElement();
            System.out.println(k + " -> " + capitals.get(k));
        }
    }
}

Output:

true
false
3
Japan -> Tokyo
France -> Paris
India -> New Delhi

Iterating Over a Hashtable

You can iterate using the modern entrySet() approach (preferred) or the legacy Enumeration.

import java.util.Hashtable;
import java.util.Map;

public class IterateHashtable {
    public static void main(String[] args) {
        Hashtable<String, Integer> population = new Hashtable<>();
        population.put("China",  1400);
        population.put("India",  1380);
        population.put("USA",     331);

        // Modern: entrySet (preferred)
        for (Map.Entry<String, Integer> entry : population.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue() + "M");
        }

        System.out.println("---");

        // Modern: forEach (Java 8+)
        population.forEach((country, pop) ->
            System.out.println(country + " = " + pop + "M"));
    }
}

Output:

USA: 331M
India: 1380M
China: 1400M
---
USA = 331M
India = 1380M
China = 1400M

No Null Keys or Values

Attempting to insert a null key or null value throws a NullPointerException immediately.

import java.util.Hashtable;

public class NullDemo {
    public static void main(String[] args) {
        Hashtable<String, String> ht = new Hashtable<>();

        try {
            ht.put(null, "value");         // throws NullPointerException
        } catch (NullPointerException e) {
            System.out.println("Null key not allowed!");
        }

        try {
            ht.put("key", null);           // throws NullPointerException
        } catch (NullPointerException e) {
            System.out.println("Null value not allowed!");
        }
    }
}

Output:

Null key not allowed!
Null value not allowed!

Warning: This is a common trap when migrating from HashMap to Hashtable. HashMap accepts one null key and multiple null values; Hashtable accepts neither.

Thread Safety

Every public method in Hashtable is declared synchronized, so only one thread can execute any method at a time. This makes Hashtable safe to share across threads without any extra locking.

import java.util.Hashtable;

public class ThreadSafeDemo {
    public static void main(String[] args) throws InterruptedException {
        Hashtable<String, Integer> counter = new Hashtable<>();
        counter.put("hits", 0);

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                // get and put are each individually synchronized
                counter.put("hits", counter.get("hits") + 1);
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start(); t2.start();
        t1.join();  t2.join();

        System.out.println("Hits: " + counter.get("hits"));
    }
}

Warning: Even though each get and put is individually atomic, the compound operation get + put above is not atomic. You can still have race conditions when reading then writing. For true atomic compound operations, prefer ConcurrentHashMap from java.util.concurrent.

Under the Hood

Data Structure

Hashtable uses an array of Entry<K,V> linked lists (buckets). When you call put(key, value):

  1. key.hashCode() is called and then re-hashed internally.
  2. The result is masked with the bucket array length to find the bucket index.
  3. The entry is prepended (in older Java) or appended to the bucket’s linked list.
  4. If a key already exists in the bucket (checked via equals()), the old value is replaced.

Rehashing

When the number of entries exceeds capacity × loadFactor, Hashtable rehashes: a new array roughly twice the current capacity is allocated, and all existing entries are redistributed. This is an O(n) operation, so choosing a good initial capacity matters for performance.

Synchronization Mechanism

Every method acquires the intrinsic lock of the Hashtable instance itself (synchronized(this)). This is coarse-grained — even unrelated read operations block each other. This is why ConcurrentHashMap (which uses fine-grained segment or bucket-level locking / CAS operations since Java 8) massively outperforms Hashtable under concurrent load.

Initial Capacity: 11 vs 16

Hashtable chose a prime number (11) as the default capacity because prime-sized tables distribute keys more evenly when using simple modulo hashing. HashMap chose a power of two (16) because it can replace expensive modulo with a fast bitwise AND — and compensates for distribution with a stronger secondary hash function.

Hashtable vs HashMap — Quick Comparison

FeatureHashtableHashMap
Thread safetyYes (synchronized)No
Null keysNot allowedOne allowed
Null valuesNot allowedAllowed
Iteration orderNot guaranteedNot guaranteed
PerformanceSlower (lock overhead)Faster
Legacy statusYes (Java 1.0)No (Java 2)
Preferred today?RarelyYes

Tip: In modern Java, prefer HashMap for single-threaded code and ConcurrentHashMap (see Concurrent Collections) for multi-threaded code. Hashtable is kept for backward compatibility.

For a more detailed side-by-side analysis, see HashMap vs Hashtable.

When to Use Hashtable

  • You are maintaining legacy code that already uses Hashtable and cannot be refactored.
  • You need a quick drop-in synchronized map and the performance penalty of coarse locking is acceptable.

In any new code, ConcurrentHashMap is almost always the better thread-safe choice.

Last updated June 13, 2026
Was this helpful?