Vector is Java's original resizable array. It shipped with Java 1.0, four years before the Collections Framework existed, and it has been sitting in java.util ever since, looking a lot like ArrayList but with every method marked synchronized. This lesson covers what Vector actually is, how its internals differ from ArrayList, why most modern code avoids it, what its synchronization actually buys you (and what it doesn't), and where you might still run into it today.
Vector<E> is a growable array of objects, backed by an internal Object[] that gets reallocated when it fills up. Functionally, it stores elements by index, lets you add, set, and remove them, and resizes itself as needed. If that description sounds like ArrayList, that's because the two classes do the same job. The difference is that Vector was written first, for a single-threaded world that turned out not to exist, and it tried to solve thread safety by synchronizing every method.
When the Collections Framework arrived in Java 1.2, Vector was retrofitted to implement List, so it slots into the same interfaces as ArrayList. You can pass a Vector anywhere a List is expected, iterate with the enhanced for loop, sort it with Collections.sort, and use it with streams.
The API looks identical to ArrayList. The same add, get, size, remove, set, and indexOf methods are there, with the same signatures. If you swapped Vector for ArrayList in this program, nothing about the output would change.
The class also keeps a handful of pre-Collections methods that ArrayList doesn't have, like elementAt, addElement, and removeElementAt. They predate the List interface and do the same things as get, add, and remove. There's no reason to use the older names in new code; they exist for backward compatibility with code written before Java 1.2.
Internally, Vector keeps two fields that drive its behavior: an Object[] elementData array that holds the elements, and an int elementCount that tracks how many slots are in use. The array's length is the capacity, which can be larger than the number of stored elements.
When you call add, Vector writes the new element at elementData[elementCount] and increments elementCount. When the count catches up to the array length, the next add triggers a resize: a new, larger array is allocated, the contents are copied over with System.arraycopy, and elementData is replaced.
The same layout as ArrayList, in other words. Where the two diverge is in how much the array grows when it fills up.
ArrayList grows by roughly 50 percent: a 10-slot array becomes 15, then 22, then 33, and so on. Vector doubles by default: 10 becomes 20, then 40, then 80. The doubling is the historical default; you can change it through the constructor.
Vector exposes a constructor that other resizable arrays don't:
The first argument is the initial capacity (the starting size of the backing array). The second is the capacity increment, the fixed number of slots added on each resize. With (16, 8), the array starts at 16 and grows by exactly 8 each time it fills: 16, then 24, then 32, then 40.
If you pass 0 for the increment (or use the no-arg or single-arg constructors), Vector falls back to doubling.
| Constructor | Initial Capacity | Growth Behavior |
|---|---|---|
new Vector<>() | 10 | Doubles when full |
new Vector<>(50) | 50 | Doubles when full |
new Vector<>(16, 8) | 16 | Adds 8 each time |
new Vector<>(16, 0) | 16 | Doubles when full |
This is the one feature Vector has that ArrayList doesn't: a knob to control growth in fixed increments rather than by ratio. In practice, almost no one needs it. Doubling works well in most cases, and the increment knob existed because 1996 hardware had much tighter memory budgets than today's.
capacity() returns the length of the underlying array, which is a method Vector exposes but ArrayList does not. After 2 adds, the array is full. The third add resizes it by the increment of 2, bumping capacity to 4. The fifth add resizes again by 2, bumping it to 6. If the increment had been 0, the third add would have doubled the capacity from 2 to 4 and the fifth would have doubled it again to 8.
Each resize allocates a new array and copies every existing element. Doubling keeps the amortized cost of add at O(1), the same as ArrayList. A small fixed increment changes that: if your Vector ends up with 10,000 elements and the increment is 10, you'll have done roughly 1,000 reallocations and copied O(n^2) elements in total. Set a sensible initial capacity, or leave the increment at 0 and let it double.
This is the part that defines Vector. The source for Vector.add, Vector.get, Vector.size, or any other public method shows the synchronized keyword on the method signature:
synchronized on an instance method means a thread has to acquire the Vector object's monitor lock before the method body runs, and it releases the lock when the method returns. While one thread holds the lock, every other thread that calls any other synchronized method on the same Vector has to wait.
The intent was simple: make the class safe to share across threads without forcing callers to think about locking. In 1996, that sounded like a reasonable default.
It looks like this in practice:
Thread A grabs the lock, runs add, and releases it. Thread B was already trying to call get, but it had to wait. Once A releases the lock, B acquires it, runs get, and releases it. Only one thread is ever inside any method of the Vector at a time.
That serialization is what makes Vector "thread-safe" at the per-method level. Three things make it less useful than it sounds.
First, even single-threaded code pays the synchronization cost. Acquiring and releasing a monitor lock isn't free, especially when the JVM can't prove the lock is uncontended. Calling add in a tight loop on a Vector is measurably slower than the same loop on an ArrayList in single-threaded code, even though no other thread is involved.
Second, in concurrent code, the per-method locking serializes all access. Two threads that just want to read from different indices have to take turns. There's no separation between readers and writers, no fine-grained locking, just one big lock around everything. As contention rises, throughput collapses.
Third, per-method synchronization does not give atomic compound operations. Consider this snippet:
Each individual call (isEmpty, size, get) is synchronized and atomic. But between them, any other thread can mutate the vector. Thread A could call isEmpty and see false, then thread B could call clear, then thread A would call cart.size() - 1, which now returns -1, and get(-1) would throw ArrayIndexOutOfBoundsException. Per-method locking doesn't help here. You'd still need an external lock around the whole sequence, the same way you would with an ArrayList.
So Vector charges every caller (including single-threaded callers) for thread safety, gives up performance under contention, and doesn't actually solve the compound-operation problem that real concurrent code runs into.
A direct comparison helps put Vector in its place.
| Aspect | Vector | ArrayList |
|---|---|---|
| Introduced in | Java 1.0 (1996) | Java 1.2 (1998) |
Implements List | Yes (retrofitted in 1.2) | Yes |
| Default initial capacity | 10 | 10 |
| Growth strategy | Doubles (configurable) | Grows by ~50% |
| Thread safety | Every method synchronized | None |
| Single-threaded performance | Slower (lock overhead) | Faster |
| Legacy enumeration methods | Yes (elements(), addElement, etc.) | No |
| Modern usage | Mostly avoided | Default List choice |
The decisive row is "thread safety". Vector was built around per-method locking; ArrayList deliberately omits it. If you don't need locking, ArrayList is cheaper. If you do, Vector's locking is too coarse and too narrow to actually solve concurrent-access problems on its own. Either way, Vector rarely comes out ahead.
A small benchmark-style demo, single-threaded:
The exact numbers depend on the JVM and the machine, but the shape is consistent: Vector is slower, even in a single-threaded loop where no other thread is competing for the lock. The JVM is doing work to acquire and release the monitor on every add, and that work adds up.
If you need a thread-safe List, Vector isn't the right answer in modern Java. Two better options exist in production code.
The first is Collections.synchronizedList(new ArrayList<>()). This wraps an existing List in a synchronized facade. Every method on the wrapper acquires a lock before delegating to the underlying list, the same way Vector does internally. The advantage over Vector is that you start from a regular ArrayList, so callers who don't need synchronization can use the unwrapped list, and you can swap implementations later (for example, to a LinkedList) without changing the wrapping code.
Functionally this is very close to Vector: every operation goes through a lock, compound operations still need external synchronization, and the per-method locking is just as coarse. The main improvement is that you have an ArrayList underneath and a clean separation between "the data structure" and "the locking policy".
The second option is CopyOnWriteArrayList, which is part of java.util.concurrent. It uses a completely different strategy: every write operation (add, set, remove) creates a fresh internal array. Readers don't take any lock at all; they just read the current array snapshot. This makes reads essentially free and writes expensive (because each write copies the whole array), which is a great fit for read-heavy scenarios like event listener lists or rarely-updated config tables.
The takeaway: when you need a thread-safe list in new code, use Collections.synchronizedList or CopyOnWriteArrayList depending on the access pattern. Don't use Vector.
Vector hasn't been deprecated, and it still appears in some codebases. A few situations to know about.
Older Java libraries return Vector from public methods, and changing the return type would break their callers. The most visible examples are in javax.swing: JTable.getSelectedRows() returns nothing related to Vector, but DefaultTableModel's constructor accepts a Vector<Vector<Object>> for its row data. Feeding rows into a Swing table requires handing a Vector to it. Code that integrates with Swing, AWT, or other early Java GUI APIs ends up using Vector because that's what the API speaks.
Legacy code in older systems (think 2000s-era Java applications, JSP pages, or anything built before the Collections Framework became dominant) often uses Vector everywhere. When maintaining or porting that code, leaving the Vector types in place is usually safer than swapping them for ArrayList, because some caller somewhere might depend on the synchronization behavior even if nothing makes that dependency visible.
Stack extends Vector. That's a piece of history rather than a design choice: Stack was written in Java 1.0 alongside Vector, and it inherits all of Vector's methods, including the synchronized ones. The modern recommendation is to use Deque (via ArrayDeque) for stack-like usage, but Stack is still in java.util for backward compatibility.
Consider an older order-processing module that uses Vector because that's what the API it integrates with expects. A legacy reporting hook hands back row data as Vector<Vector<Object>>:
There's nothing wrong with this code given the constraint that the reporting API expects Vector<Vector<Object>>. In new code, with no such constraint, you'd build the same structure with List<List<Object>> and ArrayList, and the rest of the application would be slightly faster and easier to reason about.
The rule of thumb: if you're writing new code, use ArrayList. If you need thread safety, use Collections.synchronizedList or CopyOnWriteArrayList. Use Vector only when an external API forces your hand.
10 quizzes