ArrayList vs Vector in Java: Differences and Modern Alternatives

ArrayList vs Vector in Java compared on synchronization, capacity growth and iteration, with modern replacements for Vector, Stack and Enumeration.

Capacity growth of ArrayList from 10 to 15, 22 and 33 compared with Vector doubling from 10 to 20, 40 and 80

ArrayList and Vector are both resizable-array implementations of List, but Vector synchronizes every method and doubles its capacity when full, while ArrayList has no locking and grows by about half. In the ArrayList vs Vector choice, Vector is a legacy class from Java 1.0, so new code uses ArrayList, and when several threads share a list, it uses Collections.synchronizedList() or CopyOnWriteArrayList instead of Vector.

The following example fills both classes with 11 sensor readings and shows the one difference we can observe from outside, the capacity.

List<Integer> readings = new ArrayList<>();                           // no locking
Vector<Integer> legacy = new Vector<>();                                // every method synchronized
for (int i = 0; i < 11; i++) {
    readings.add(i);
    legacy.add(i);
}
int vectorCapacity = legacy.capacity();                                 // 20, doubled from 10
int listSize = readings.size();                                         // 11, ArrayList has no capacity() method
List<Integer> shared = Collections.synchronizedList(new ArrayList<>()); // the modern thread-safe choice

Notice that Vector exposes capacity() while ArrayList hides it. We compare the two classes in a table, look at what Vector‘s locking does and does not protect, compare growth and iteration, and finish with the modern replacement for each Vector use case.

1. Differences Between ArrayList and Vector

Both classes store elements in an Object[] array, keep insertion order, allow duplicates and null, and give constant-time access by index. The differences come from their age, because Vector existed before the Collections Framework and was retrofitted to implement List in Java 1.2.

Capacity growth of ArrayList from 10 to 15, 22 and 33 compared with Vector doubling from 10 to 20, 40 and 80
ArrayList adds half its capacity on each resize, while Vector doubles it
FeatureArrayListVector
SinceJava 1.2Java 1.0 (legacy)
SynchronizationNoneEvery public method locks the Vector
Growth when fullAbout 50% (10, 15, 22, 33 on JDK 25)100% by default, or a fixed capacityIncrement
Capacity visibleNoYes, capacity()
IteratorFail-fastFail-fast
EnumerationNoneelements(), not fail-fast
Extra legacy methodsNoneaddElement(), elementAt(), removeElement() and others
SubclassNone in the JDKStack
Use in new codeDefault ListOnly for legacy APIs that require it

When we do not need a thread-safe implementation, Vector is replaced by ArrayList, which does the same job without the locking.

2. Synchronization in Vector

Every public method of Vector that reads or changes elements runs under the synchronized lock of the Vector object, so a thread must hold that lock for each call. Two threads calling add() at the same time therefore cannot corrupt the array, which can happen with a plain ArrayList.

Locking each method does not make a sequence of calls atomic. A check followed by an action, such as “add this device if it is not in the list yet”, releases the lock between contains() and add(), so another thread can run in between.

static void registerUnsafe(Vector<String> devices, String id) {
    if (!devices.contains(id)) {          // threads A and B both see false
        devices.add(id);                  // both add the same id
    }
}

For example, a smart-home hub receives two “device online” messages for the same thermostat on two threads within a millisecond. With registerUnsafe(), the thermostat can end up twice in the list, and the dashboard shows two tiles for it. We need a lock around the whole check-and-add sequence, or a method that does both steps atomically.

static void registerSafe(List<String> devices, String id) {
    synchronized (devices) {
        if (!devices.contains(id)) {
            devices.add(id);
        }
    }
}
List<String> devices = Collections.synchronizedList(new ArrayList<>());
registerSafe(devices, "thermostat");
registerSafe(devices, "thermostat");
int registered = devices.size();                                        // 1
CopyOnWriteArrayList<String> listeners = new CopyOnWriteArrayList<>();
boolean first = listeners.addIfAbsent("thermostat");                    // true
boolean second = listeners.addIfAbsent("thermostat");                   // false, already present

The synchronized block works the same with a Vector, a synchronized wrapper or any other list, as long as every thread locks on the same object. So the locking built into Vector gives no extra safety over Collections.synchronizedList(), and it costs a lock on every call even in single-threaded code. The tutorial on synchronizing an ArrayList covers both modern options in detail.

3. Capacity Growth

When the backing array is full, Vector doubles its capacity by default, so a default Vector goes 10, 20, 40, 80. On JDK 25, an ArrayList grows by half its capacity, going 10, 15, 22, 33. Doubling means fewer copies for a fast-growing list but more unused slots, up to half the array right after a resize.

A Vector can grow by a fixed step instead. The constructor Vector(int initialCapacity, int capacityIncrement) adds capacityIncrement slots on each resize, while ArrayList only lets us choose the initial capacity.

Vector<Integer> stepped = new Vector<>(10, 5);
for (int i = 0; i < 11; i++) {
    stepped.add(i);
}
int steppedCapacity = stepped.capacity();                               // 15, grew by the fixed increment

Both classes have ensureCapacity() to reserve room before a large batch and trimToSize() to drop unused slots. A small fixed increment on a large Vector causes many resizes, because each one copies the whole array to add only a few slots.

4. Iterator vs Enumeration

Both classes return fail-fast iterators from iterator() and listIterator(). When the list changes structurally outside the iterator, the next call to next() throws ConcurrentModificationException on a best-effort basis.

static void addDuringLoop(List<String> sensors) {
    Iterator<String> it = sensors.iterator();
    while (it.hasNext()) {
        if (it.next().equals("attic")) {
            sensors.add("yard");
        }
    }
}
addDuringLoop(new ArrayList<>(List.of("hall", "attic", "porch")));    // ConcurrentModificationException
addDuringLoop(new Vector<>(List.of("hall", "attic", "porch")));       // ConcurrentModificationException

The older Enumeration from Vector.elements() is not fail-fast. It keeps going after a change, so the loop sees the new element, and the results of such a loop are undefined.

Vector<String> sensors = new Vector<>(List.of("hall", "attic", "porch"));
Enumeration<String> names = sensors.elements();
StringBuilder seen = new StringBuilder();
while (names.hasMoreElements()) {
    String name = names.nextElement();
    if (name.equals("attic")) {
        sensors.add("yard");
    }
    seen.append(name).append(' ');
}
String visited = seen.toString().strip();                               // "hall attic porch yard", no exception

A missing exception is not a feature here. A fail-fast iterator reports the bug on the spot, whereas the Enumeration loop can skip or repeat elements when another thread removes one. To change a list while looping, we use removeIf() or the iterator’s own remove() on either class.

5. Replacing Vector in Modern Code

Code that still uses Vector does so for one of a few reasons, and each one has a modern replacement in java.util or java.util.concurrent.

Old codeModern replacementWhy
Vector as a general listArrayListNo lock on every call
Vector shared between threadsCollections.synchronizedList() or CopyOnWriteArrayListSame locking, or lock-free reads
StackArrayDeque with push() and pop()Recommended by the Stack Javadoc
Enumeration loopEnhanced for loop over the list, or asIterator() (Java 9) when only an Enumeration is availableThe list’s own iterator is fail-fast; asIterator() only adapts the old type
elementAt(i), addElement(e)get(i), add(e)Standard List methods

Converting between the two classes is one constructor call, because each one accepts any Collection. Collections.list() turns an Enumeration into an ArrayList when an old API returns one.

Vector<String> old = new Vector<>(List.of("hall", "attic"));
List<String> modern = new ArrayList<>(old);                             // [hall, attic]
Vector<String> forLegacyApi = new Vector<>(modern);                     // [hall, attic]
ArrayList<String> fromEnumeration = Collections.list(old.elements());   // [hall, attic]
Deque<String> undo = new ArrayDeque<>();
undo.push("type");
undo.push("bold");
String lastAction = undo.pop();                                         // "bold"

For example, Swing’s DefaultTableModel still takes Vector arguments in some constructors. We keep the data in an ArrayList everywhere else and convert with new Vector<>(rows) only at that call, so the legacy type stays at the edge of the app. For stacks and queues, the ArrayDeque tutorial shows the full API.

6. ArrayList vs Vector FAQs

Developers ask about Vector mostly while reading or migrating older code.

6.1. Is Vector Deprecated in Java?

No. Vector is not annotated with @Deprecated in Java 25, and it still works. It is a legacy class, and ArrayList replaces it when thread safety is not needed.

6.2. Is Vector Thread-Safe?

Each single method call is thread-safe, but sequences of calls are not. Check-and-add code and iteration need an external synchronized block on the Vector, as we saw in section 2.

6.3. Which Is Faster, ArrayList or Vector?

ArrayList does less work per call because it takes no lock. The gap depends on the JVM and on contention, so we measure with JMH before quoting numbers. When many threads share a list, the bigger cost is threads waiting for each other on the single lock, which affects Vector and Collections.synchronizedList() alike.

6.4. Should We Use Stack for a Stack?

No. Stack extends Vector, so it inherits the per-call locking and list methods that let code insert in the middle of a stack. The Deque interface replaces it, and ArrayDeque is the standard implementation for it.

6.5. Can ArrayList and Vector Store null?

Yes, both allow null elements and duplicates. The concurrent alternative CopyOnWriteArrayList allows null too, while List.of() rejects it.

7. Conclusion

ArrayList and Vector store data the same way, but Vector locks every call and doubles its capacity, while ArrayList takes no lock and grows by about half. The per-call locking does not protect check-and-add code or iteration, so it rarely gives the safety people expect from it.

In new code, we use ArrayList for single-threaded lists, Collections.synchronizedList() or CopyOnWriteArrayList for shared ones and ArrayDeque instead of Stack. Vector appears only where an old API demands it, converted at the boundary.

8. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.