Skip to content
Java collections 7 min read

ArrayList vs Vector

ArrayList and Vector look almost identical on the surface — both implement the List interface, both back their elements with a resizable array, and both have been in the JDK for decades. The crucial difference is that Vector was built in Java 1.0 and every single one of its public methods is synchronized, while ArrayList (added in Java 1.2) has no synchronization at all. That one design decision shapes every trade-off between them.

The Core Difference at a Glance

FeatureArrayListVector
IntroducedJava 1.2Java 1.0
SynchronizationNoneEvery method is synchronized
Thread-safe?No (by default)Yes (but coarsely)
Growth factor50% (oldCapacity + oldCapacity >> 1)100% (doubles) or custom increment
PerformanceFaster in single-threaded codeSlower due to lock overhead
Preferred today?YesRarely — use safer alternatives
Legacy statusActiveEffectively legacy

Basic Usage — Identical API

Because both classes implement List, you can swap them by changing only the constructor:

import java.util.ArrayList;
import java.util.Vector;
import java.util.List;

public class BasicDemo {
    public static void main(String[] args) {
        List<String> arrayList = new ArrayList<>();
        List<String> vector    = new Vector<>();

        arrayList.add("Java");
        arrayList.add("Python");
        vector.add("Java");
        vector.add("Python");

        System.out.println("ArrayList: " + arrayList);
        System.out.println("Vector:    " + vector);
        System.out.println("ArrayList size: " + arrayList.size());
        System.out.println("Vector size:    " + vector.size());
    }
}

Output:

ArrayList: [Java, Python]
Vector:    [Java, Python]
ArrayList size: 2
Vector size:    2

Note: Because both implement List, any method that accepts a List<T> parameter works with either class without any changes.

Synchronization — The Big Deal

Vector’s synchronization means that when one thread calls add(), get(), or remove(), it must acquire the object’s intrinsic lock before proceeding. Every other thread trying to call any method on that same Vector is blocked until the lock is released.

import java.util.Vector;

public class VectorSyncDemo {
    public static void main(String[] args) throws InterruptedException {
        Vector<Integer> vector = new Vector<>();

        // Two threads safely adding to Vector
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) vector.add(i);
        });
        Thread t2 = new Thread(() -> {
            for (int i = 5; i < 10; i++) vector.add(i);
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Size: " + vector.size()); // always 10
        System.out.println(vector);
    }
}

Output (order of elements may vary):

Size: 10
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

This looks safe — and the size() call is indeed always 10 — but Vector’s synchronization has a hidden trap: it synchronizes individual method calls, not compound actions.

// DANGER: still not thread-safe, even with Vector!
if (!vector.isEmpty()) {          // lock acquired and released
    Object o = vector.get(0);    // lock acquired and released — another thread may have removed index 0 between these two calls!
}

Warning: Method-level synchronization does not protect you from check-then-act races. For truly safe compound operations you still need external synchronization or a purpose-built concurrent collection like CopyOnWriteArrayList.

Performance Impact of Synchronization

Every synchronized method call involves acquiring and releasing a monitor lock. Even when only one thread is running, the JVM must handle the lock bookkeeping. Here is a simplified benchmark that illustrates the overhead:

import java.util.ArrayList;
import java.util.Vector;
import java.util.List;

public class PerfDemo {
    static final int N = 1_000_000;

    public static void main(String[] args) {
        List<Integer> al = new ArrayList<>(N);
        List<Integer> v  = new Vector<>(N);

        long start = System.nanoTime();
        for (int i = 0; i < N; i++) al.add(i);
        long alTime = System.nanoTime() - start;

        start = System.nanoTime();
        for (int i = 0; i < N; i++) v.add(i);
        long vTime = System.nanoTime() - start;

        System.out.printf("ArrayList add: %d ms%n", alTime / 1_000_000);
        System.out.printf("Vector    add: %d ms%n", vTime  / 1_000_000);
    }
}

Output (approximate, single thread):

ArrayList add: 18 ms
Vector    add: 45 ms

Vector can be 2–3× slower than ArrayList in single-threaded benchmarks purely because of the lock overhead. The JIT compiler can sometimes eliminate redundant locks via lock elision, but this optimisation is not guaranteed.

Growth Strategy

Both classes grow their internal array when capacity is exceeded, but they use different strategies:

  • ArrayList grows to oldCapacity + (oldCapacity >> 1) — that is, 50% more than the current size.
  • Vector doubles to 2 × oldCapacity by default. You can also pass a capacityIncrement to the constructor to set a fixed growth step.
import java.util.Vector;

public class VectorGrowthDemo {
    public static void main(String[] args) {
        // Capacity increment of 5 — grows by exactly 5 slots each time
        Vector<String> v = new Vector<>(4, 5);

        for (int i = 0; i < 10; i++) v.add("item" + i);
        System.out.println("Size: " + v.size());
        // internal capacity is now 4 + 5 + 5 = 14 after two growths
    }
}

Output:

Size: 10

Tip: Vector’s doubling strategy wastes more memory on average than ArrayList’s 50% growth. For large lists this difference adds up quickly.

Vector-Specific Methods

Vector exposes several legacy methods that predate the List interface. They still work, but modern code should prefer the standard List API equivalents:

import java.util.Vector;

public class LegacyMethodsDemo {
    public static void main(String[] args) {
        Vector<String> v = new Vector<>();
        v.addElement("Alpha");   // same as add()
        v.addElement("Beta");
        v.addElement("Gamma");

        System.out.println(v.elementAt(1));       // same as get(1)
        System.out.println(v.firstElement());     // same as get(0)
        System.out.println(v.lastElement());      // same as get(size()-1)
        System.out.println(v.capacity());         // current internal array size (10)
        v.removeAllElements();                    // same as clear()
        System.out.println("After clear: " + v.size());
    }
}

Output:

Beta
Alpha
Gamma
10
After clear: 0

Note: Methods like addElement(), elementAt(), and removeAllElements() exist only on Vector. Avoid them in new code — they make it harder to swap the implementation later.

When to Use Each

Use ArrayList when:

  • You are writing single-threaded code (the vast majority of cases).
  • You need maximum read/iteration performance.
  • You want to add thread-safety yourself with a more targeted approach.

Use Vector when:

  • You are maintaining legacy code that already uses Vector and the cost of migration outweighs the benefit.
  • Practically speaking, you almost never need to reach for Vector in new code.

The modern alternatives to Vector

If you genuinely need a thread-safe List, these are your options — all better than Vector:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ThreadSafeAlternatives {
    public static void main(String[] args) {
        // Option 1: wrap an ArrayList with a synchronized wrapper
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());

        // Option 2: CopyOnWriteArrayList — reads are lock-free, writes copy the array
        List<String> cowList = new CopyOnWriteArrayList<>();

        syncList.add("hello");
        cowList.add("hello");

        System.out.println(syncList);
        System.out.println(cowList);
    }
}

Output:

[hello]
[hello]
Thread-safe optionBest for
Collections.synchronizedList(new ArrayList<>())Low-contention, general-purpose thread-safety
CopyOnWriteArrayListMany reads, very few writes (e.g., listener lists)
VectorLegacy code only

Tip: CopyOnWriteArrayList makes a full array copy on every write, so it is only efficient when reads vastly outnumber writes. For high-write concurrent scenarios, consider a ConcurrentLinkedQueue or other purpose-built concurrent structure from java.util.concurrent.

Under the Hood

Lock Elision by the JIT

Modern JVMs (via the JIT compiler) can perform lock elision — if escape analysis proves that a Vector instance cannot be accessed by more than one thread, the JIT may remove the lock overhead entirely. In practice this optimisation is unreliable across JVM versions, and you should never rely on it as a justification for using Vector.

modCount and Fail-Fast Iterators

Like ArrayList, Vector tracks structural modifications with a modCount counter. If you modify a Vector while iterating over it using an Iterator or enhanced for loop, a ConcurrentModificationException is thrown. This is the fail-fast behaviour shared by most java.util collections.

Relationship to Stack

Java’s legacy Stack class extends Vector, inheriting all its synchronization and growth behaviour. This is widely considered a design mistake — Stack is better replaced by ArrayDeque in modern code, just as Vector is better replaced by ArrayList.

Memory Layout

Both ArrayList and Vector store a reference to an Object[] backing array plus a handful of int fields (size, modCount, capacityIncrement for Vector). The actual element objects live elsewhere on the heap; the array holds only their references (4 or 8 bytes each, depending on JVM pointer compression). Garbage collection reclaims unreferenced elements automatically.

Quick Decision Cheat Sheet

QuestionAnswer
Single-threaded code?ArrayList — always
Need thread-safety?CopyOnWriteArrayList or Collections.synchronizedList()
Maintaining old code that uses Vector?Keep Vector, migrate when opportunity allows
Need a stack?ArrayDeque (not Stack / not Vector)
Need enumeration over elements?Prefer Iterator; Vector.elements() is legacy
  • ArrayList — deep dive into ArrayList internals, capacity management, and methods
  • Vector — full reference for the Vector class and its legacy API
  • Concurrent CollectionsCopyOnWriteArrayList, ConcurrentHashMap, and other thread-safe containers
  • Synchronization — how Java monitors and synchronized work under the hood
  • List Interface — the common contract shared by ArrayList, Vector, and LinkedList
  • Collections Utility ClasssynchronizedList(), unmodifiableList(), and other wrappers
Last updated June 13, 2026
Was this helpful?