Collections ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-26

Legacy Classes in Java: Vector, Stack, Hashtable and Enumeration

Store a port number as an integer — props.put("port", 8080) — and props.getProperty("port") returns null, even though props.get("port") happily shows 8080. The reason is history: Properties dates back to Java 1.0 and extends Hashtable<Object, Object>, so it accepts any object, while getProperty() only sees strings. Legacy classes in Java are full of surprises like this one.

Legacy classes in Java are the classes and the interface of the java.util package that appeared in Java 1.0, before the Collections Framework (Java 1.2): Vector, Stack, Dictionary, Hashtable, Properties and the Enumeration interface. Java 1.2 retrofitted them into the new collection hierarchy, and Java 5 made most of them generic. None of them is marked @Deprecated, but new code uses ArrayList, ArrayDeque, HashMap and ConcurrentHashMap instead.

What is a legacy class in Java

A legacy class in Java is one of six types from Java 1.0 that stored objects before the Collections Framework existed: the dynamic array Vector, the stack Stack, the abstract dictionary Dictionary, the hash table Hashtable, the string settings store Properties and the iteration interface Enumeration. They were kept for backward compatibility, so that old code still compiles and runs on new Java versions.

These classes had no common foundation: each used its own method names (addElement(), elementAt(), keys()), and iteration went through Enumeration. Every method of Vector and Hashtable is synchronized, so single-threaded code pays for locks it does not need. Their modern counterparts are covered in Java Collections Framework.

Legacy classes in Java: Vector, Stack, Dictionary, Hashtable, Properties and the Enumeration interface

Class / interface What it is Place in the hierarchy since Java 1.2 Modern replacement
Vector<E> Synchronized dynamic array Extends AbstractList, implements List ArrayList
Stack<E> LIFO stack Subclass of Vector ArrayDeque through the Deque interface
Dictionary<K, V> Abstract class for key-value pairs Outside the Collections Framework The Map interface
Hashtable<K, V> Synchronized hash table, null not allowed Subclass of Dictionary, implements Map HashMap; in multithreaded code — ConcurrentHashMap
Properties “String → string” settings, reading and writing .properties files Subclass of Hashtable<Object, Object> No replacement — still in use
Enumeration<E> Interface for sequential iteration Outside the Collections Framework Iterator

You still run into these classes today: in code written before the 2000s, in APIs that return Enumeration (ClassLoader.getResources(), NetworkInterface.getNetworkInterfaces()), in System.getProperties() and in DriverManager.getConnection(url, properties) from JDBC.

Legacy classes vs utility classes

A utility class is a class with only static methods that you never instantiate: Collections, Arrays, Objects, Math. “Legacy” describes the age of a class, “utility” describes its design. Collections arrived in Java 1.2 together with the new framework and is not considered legacy; its methods are covered in Collections Class in Java.

The Vector class

Vector is a dynamic array from Java 1.0, similar to ArrayList with two differences: all of its methods are synchronized, and besides the List methods it keeps old ones such as addElement() and elementAt(). The default capacity is 10 elements; when the array fills up, it doubles.

In Java 1.2 Vector was reworked into a subclass of AbstractList that implements the List interface, and in Java 5 it became generic. Through List it also implements Iterable, so you can loop over it with for-each:

public class Vector<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, Serializable

The old methods work side by side with the List methods. Two of them take their arguments in reverse order — an easy mistake when porting code:

Vector method List counterpart Gotcha
addElement(E obj) add(E e) Returns void, not boolean
elementAt(int index) get(int index) Invalid index — ArrayIndexOutOfBoundsException
setElementAt(E obj, int index) set(int index, E e) Element first, then index; returns nothing
insertElementAt(E obj, int index) add(int index, E e) Element first, then index
removeElement(Object obj), removeElementAt(int index) remove(Object o), remove(int index) Distinct names avoid the remove(1) confusion of a List<Integer>
firstElement(), lastElement() getFirst(), getLast() (Java 21) Empty vector — NoSuchElementException
elements() iterator() Returns an Enumeration that is not fail-fast (see Enumeration)
capacity() None Current length of the internal array

How the capacity grows and how the old methods behave:

import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        Vector<Integer> v = new Vector<>();
        System.out.println(v.capacity());
        for (int i = 0; i < 11; i++) {
            v.add(i);
        }
        System.out.println(v.capacity());   // the 11th element doubled the array

        Vector<Integer> step = new Vector<>(3, 2);  // capacity 3, growth step 2
        for (int i = 0; i < 4; i++) {
            step.addElement(i);
        }
        System.out.println(step.capacity());
        step.setElementAt(99, 0);           // element first, then index
        System.out.println(step);
        System.out.println(step.elementAt(3) + " " + step.lastElement());
    }
}

Output:

10
20
5
[99, 1, 2, 3]
3 3

The Vector(initialCapacity, capacityIncrement) constructor sets a fixed growth step: instead of doubling, the array grew from 3 to 5. ArrayList has no such parameter and grows by a factor of 1.5.

Synchronized does not mean thread-safe

Each Vector method is synchronized on its own, but a combination of calls is not. The code if (!v.contains(x)) v.add(x); running in two threads can add x twice: another thread can slip in between contains() and add(). Iterating over a vector also has to be wrapped manually in synchronized (v) { ... }. That is why the built-in synchronization of Vector and Hashtable rarely saves multithreaded code — use the classes from java.util.concurrent instead (overview of thread-safe collections).

The Stack class

Stack is a subclass of Vector that implements a LIFO stack (“last in, first out”) with five methods: push(), pop(), peek(), empty() and search(). It has a single no-argument constructor that creates an empty stack. Since Java 5 the class is generic: public class Stack<E> extends Vector<E>.

The Java documentation recommends the Deque interface and the ArrayDeque class instead of Stack. Methods with the same names behave differently there on an empty stack, and search() has no counterpart at all:

Stack method What it does Empty stack Deque counterpart
E push(E item) Puts an element on top and returns it — push(e), returns void; null in ArrayDeque — NullPointerException
E pop() Removes and returns the top element EmptyStackException pop() — NoSuchElementException
E peek() Returns the top element without removing it EmptyStackException peek() — returns null, no exception
boolean empty() Checks whether the stack is empty true isEmpty()
int search(Object o) Position of the element, counting from 1 at the top -1 None

What does this code print?

import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        stack.push("A");
        stack.push("B");
        stack.push("C");

        System.out.println(stack.peek());
        System.out.println(stack.search("C"));
        System.out.println(stack.search("A"));
        System.out.println(stack.search("Z"));
        System.out.println(stack.pop());
        System.out.println(stack.get(0));   // Vector method: access to the bottom of the stack

        stack.clear();
        stack.pop();
    }
}

Output:

C
1
3
-1
C
A
Exception in thread "main" java.util.EmptyStackException

search() counts positions not from 0, like Vector indexes, but from 1 and from the top: the last pushed "C" is at position 1, the bottom "A" at 3. The inherited get(0) reaches the bottom of the stack and breaks the LIFO principle. This is the main design complaint about Stack; how it compares with ArrayDeque and LinkedList is covered in the ArrayDeque lesson.

The Dictionary class

Dictionary<K, V> is an abstract class from Java 1.0 for storing key-value pairs: a value is stored under a key and retrieved by the same key, as in the Map interface. Its Javadoc explicitly calls the class obsolete: new implementations should implement Map rather than extend Dictionary.

Dictionary has only abstract methods: put(K, V), get(Object), remove(Object), size(), isEmpty(), plus keys() and elements(), which return an Enumeration of keys and values. It is a class, not an interface, so a subclass cannot extend anything else — one of the reasons the Map interface appeared in Java 1.2. The class carries no @Deprecated annotation, and the compiler gives no warnings. Since Java 5 it is generic:

public abstract class Dictionary<K, V>

In practice Dictionary only shows up as the parent of Hashtable: modern code never declares variables of this type.

The Hashtable class

Hashtable<K, V> is a concrete implementation of Dictionary that stores key-value pairs in a hash table. In Java 1.2 the class was adapted to the Map interface, so it works like HashMap with two differences: all methods are synchronized, and neither a key nor a value can be null — an attempt throws NullPointerException.

public class Hashtable<K, V> extends Dictionary<K, V>
        implements Map<K, V>, Cloneable, Serializable

Let us compare it with HashMap on the same data:

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

public class HashtableExample {
    public static void main(String[] args) {
        Hashtable<String, Integer> table = new Hashtable<>();
        table.put("Java", 1995);

        System.out.println(table.contains(1995));     // searches VALUES
        System.out.println(table.containsKey(1995));

        try {
            table.put("Kotlin", null);
        } catch (NullPointerException e) {
            System.out.println("Hashtable: null value - NullPointerException");
        }
        try {
            table.put(null, 2011);
        } catch (NullPointerException e) {
            System.out.println("Hashtable: null key - NullPointerException");
        }

        Map<String, Integer> map = new HashMap<>();
        map.put(null, 2011);
        map.put("Kotlin", null);
        System.out.println(map);
    }
}

Output:

true
false
Hashtable: null value - NullPointerException
Hashtable: null key - NullPointerException
{null=2011, Kotlin=null}

The old contains(value) method is the same as containsValue(value), although its name suggests a key lookup. The old way to iterate over keys and values is keys() and elements(), which return an Enumeration. In single-threaded code Hashtable is replaced with HashMap, in multithreaded code with ConcurrentHashMap. The internals of HashMap and how it differs from TreeMap and Hashtable are explained in How HashMap works internally.

The Properties class

Properties is a subclass of Hashtable for storing settings where both the key and the value are strings. It reads and writes .properties files, can return a default value and can look up missing keys in a separate set of defaults. It is the only legacy class that is still routinely used in new code.

The class declaration explains the example at the start of this lesson:

public class Properties extends Hashtable<Object, Object>

Properties did not become generic in Java 5: it extends Hashtable<Object, Object>, so the inherited put() and putAll() accept any object. The methods of Properties itself expect strings only: getProperty() returns null for a value that is not a String, and store() throws ClassCastException on such a value. Write settings only through setProperty(String, String). Since Java 9 the data is stored internally in a ConcurrentHashMap, but the inheritance from Hashtable is kept for compatibility.

Method What it does Edge case
String getProperty(String key) Value for the key; if absent, looks it up in the defaults Not found anywhere or not a String — null
String getProperty(String key, String defaultValue) The same, but returns defaultValue instead of null The default value is not stored in the Properties object
Object setProperty(String key, String value) Stores a string pair, returns the previous value null key or value — NullPointerException
void load(Reader reader), void load(InputStream in) Reads pairs from text in the .properties format load(InputStream) reads ISO-8859-1: characters outside Latin-1 without \uXXXX escapes get corrupted
void store(Writer writer, String comments) Writes the pairs and a comment with the date A non-string key or value — ClassCastException
Set<String> stringPropertyNames() All string keys, including keys from the defaults (Java 6) Skips pairs with non-string keys or values
Enumeration<?> propertyNames() The old way to iterate over keys, including the defaults A non-string key — ClassCastException

The typical usage is default settings plus a loaded file. To make the example runnable without a file, the text is read from a StringReader:

import java.io.IOException;
import java.io.StringReader;
import java.util.Properties;

public class PropertiesExample {
    public static void main(String[] args) throws IOException {
        Properties defaults = new Properties();
        defaults.setProperty("port", "80");

        Properties config = new Properties(defaults);
        config.load(new StringReader("""
                host=localhost
                port=8080
                # comment
                user = Anna
                """));

        System.out.println(config.getProperty("port"));
        System.out.println(config.getProperty("user"));
        config.remove("port");
        System.out.println(config.getProperty("port"));          // taken from defaults
        System.out.println(config.getProperty("timeout", "30"));
        System.out.println(config.size());                      // defaults are not counted

        Properties props = new Properties();
        props.put("port", 8080);                                 // Integer, not String
        System.out.println(props.get("port") + " / " + props.getProperty("port"));
    }
}

Output:

8080
Anna
80
30
2
8080 / null

A line starting with # is a comment, and spaces around = are dropped. Defaults are not copied into the object: after remove("port") the value comes from defaults, and size() counts only the object’s own pairs host and user. The """ text block compiles starting with Java 15; on older versions, join the lines with \n.

Non-Latin characters in a .properties file

props.load(new FileInputStream("app.properties")) reads the file as ISO-8859-1, so a line like city=München saved in UTF-8 turns into garbage (München), and Cyrillic, Greek or Chinese text is corrupted the same way. Read the file through a UTF-8 Reader instead: try (Reader r = Files.newBufferedReader(Path.of("app.properties"))) { props.load(r); } — Files.newBufferedReader() uses UTF-8 by default.

The Enumeration interface

Enumeration<E> is a Java 1.0 interface for iterating over elements one at a time: hasMoreElements() checks whether elements remain, and nextElement() returns the next one. It was superseded by Iterator, but Vector, Hashtable, Properties and a number of other APIs still return Enumeration, which is why Java 5 made it generic.

public interface Enumeration<E>
Method What it does Edge case
boolean hasMoreElements() Whether more elements remain Can be called any number of times in a row
E nextElement() Returns the next element No elements left — NoSuchElementException
default Iterator<E> asIterator() Turns an Enumeration into an Iterator (Java 9) remove() on that iterator — UnsupportedOperationException

Enumeration cannot remove elements, and the enumerations returned by Vector.elements() and Hashtable.keys()/elements() are not fail-fast: they do not notice changes to the collection. What does this code print?

import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

public class EnumerationExample {
    public static void main(String[] args) {
        Vector<String> v = new Vector<>(List.of("A", "B", "C"));

        Enumeration<String> en = v.elements();
        while (en.hasMoreElements()) {
            String s = en.nextElement();
            if (s.equals("A")) {
                v.remove(s);
            }
            System.out.print(s + " ");
        }
        System.out.println();
        System.out.println(v);

        // Bridging the old and new APIs
        List<String> copy = Collections.list(v.elements());   // Enumeration -> ArrayList
        Enumeration<String> back = Collections.enumeration(copy); // Collection -> Enumeration
        back.asIterator().forEachRemaining(System.out::print);  // Java 9
    }
}

Output:

A C
[B, C]
BC

Element "B" is skipped without any error: after "A" was removed, "B" shifted to position 0, while the enumeration had already moved on to position 1. An iterator over the same vector would throw ConcurrentModificationException here (the mechanism is explained in the Iterator lesson). A silent skip is more dangerous than an exception: you notice the bug only from wrong data.

Feature Enumeration Iterator
Introduced in Java 1.0 Java 1.2
Traversal methods hasMoreElements(), nextElement() hasNext(), next()
Removal during traversal No remove()
Reaction to collection changes For Vector and Hashtable — does not notice, result is undefined Fail-fast: ConcurrentModificationException
Where you meet it Legacy classes, StringTokenizer, ClassLoader.getResources(), SequenceInputStream All collections of the Collections Framework

Enumeration is not marked @Deprecated: the documentation only advises new implementations to prefer Iterator. If an API returns an Enumeration, convert it right away into an Iterator with asIterator() or into a list with Collections.list().

What to use instead of legacy classes

Replace Vector with ArrayList, Stack with ArrayDeque through the Deque interface, Hashtable with HashMap or ConcurrentHashMap, Dictionary with the Map interface, and Enumeration with Iterator. Keep Properties: the JDK has no direct replacement for settings files. The switch usually comes down to changing a declaration, but a few behavioral differences are worth checking in advance.

Legacy class Single-threaded replacement Multithreaded replacement What to check when migrating
Vector ArrayList CopyOnWriteArrayList or Collections.synchronizedList() Calls to addElement(), elementAt(), setElementAt() — set() takes its arguments in reverse order
Stack ArrayDeque (variable type — Deque) ConcurrentLinkedDeque, LinkedBlockingDeque peek() on an empty stack returns null instead of throwing; null elements are not allowed; iteration starts at the top
Hashtable HashMap ConcurrentHashMap HashMap accepts null: code that relied on NullPointerException will silently store null; contains() → containsValue()
Dictionary The Map interface The ConcurrentMap interface Your own subclasses of Dictionary are rewritten as implements Map or extends AbstractMap
Enumeration Iterator, for-each — Third-party APIs that return Enumeration are wrapped with asIterator()
Properties Stays Properties Stays Properties (its methods are thread-safe) put() → setProperty(), get() → getProperty()

After the switch, declare the variable with the interface type: List<String> names = new ArrayList<>();, Deque<Integer> stack = new ArrayDeque<>();, Map<String, Integer> counts = new HashMap<>();. Then the implementation can be swapped in a single line if needed.

Frequently Asked Questions

Which other Java classes are considered legacy, apart from collections?

The Javadoc explicitly calls StringTokenizer a legacy class: use String.split() or Scanner instead. java.util.Date and Calendar are the legacy date API, replaced by the java.time package (Java 8). BitSet, on the other hand, also dates from Java 1.0 but is not legacy and is still in use.

Are Vector, Stack and Hashtable marked @Deprecated?

No. The compiler gives no warnings when you use them, and there are no plans to remove them from the JDK. Only the documentation says they are outdated: the Stack Javadoc recommends Deque, and the Dictionary Javadoc calls the class obsolete. That is why static analyzers (SonarQube, IntelliJ IDEA inspections), not the compiler, help you find such classes in a project.

What is the difference between Hashtable and HashMap?

A classic interview question. Hashtable appeared in Java 1.0, is synchronized and forbids null keys and values; HashMap appeared in Java 1.2, is not synchronized, allows one null key and any number of null values and is faster in a single thread. Iterators of both are fail-fast, but Hashtable also has the non-fail-fast Enumeration from keys() and elements(). A runnable comparison is in the section The Hashtable class.

Can I pass an ArrayList to a method that expects a Vector?

Not directly: ArrayList does not extend Vector, they are unrelated classes. You have to make a copy: new Vector<>(list). The reverse conversion is new ArrayList<>(vector), and if an old method expects an Enumeration, use Collections.enumeration(list). If you own the method, change the parameter type to List so that both implementations fit.

Comments

Please log in or register to have a possibility to add comment.