Java Vector
The synchronized Vector class in Java, why it's legacy, and when (rarely) to still use it.
Java Vector
Vector<E> is the original resizable-array List — it shipped in Java 1.0, four years before the Collections Framework existed. When Java 1.2 added ArrayList, it was carefully retrofitted to implement List so existing Vector code didn't break. Three decades later it's still in the standard library, still functioning, and still the wrong choice for almost every new piece of code. This chapter is short on purpose: you need to know what Vector is so you can recognise it in old code, not because you'll be writing new code that uses it.
What's actually different from ArrayList
Vector is a resizable-array-backed List, just like ArrayList. Two differences matter:
- Every public method is
synchronized. Everyadd,get,set,remove,size,iterator— they all acquire theVector's monitor on entry. The intention in 1995 was thread safety; the practical effect is per-method locking that's coarse, slow, and rarely correct (more on that below). - The growth policy is different. By default, when the backing array fills up,
Vectordoubles it.ArrayListgrows by about 50%. Doubling wastes more memory on average; 50% growth wastes less. Neither matters in practice unless you're managing millions of small lists.
That's it. Every other observable behaviour is the same: O(1) random access, O(n) insert at the front, fail-fast iterators, same generic interface.
Why "thread-safe" isn't enough
The per-method synchronized is exactly as much synchronisation as a single call needs and exactly the wrong granularity for everything else. Consider check-then-act:
Vector<String> v = ...;
if (!v.contains(\"hello\")) { // synchronised → atomic
v.add(\"hello\"); // synchronised → atomic
} // BUT: NOT atomic togetherTwo Vector calls are each atomic. The combination isn't. Between the contains check and the add, another thread can sneak in a competing add. The lock you wanted is one that covers both calls, not each call individually. To get it you write synchronized (v) { ... } around the whole block — at which point you've replicated what Collections.synchronizedList(arrayList) already does, only on an awkward older class.
The same trap kills iteration:
for (String s : v) { ... } // many internal hasNext/next calls, none locked togetherA concurrent mutation in the middle throws ConcurrentModificationException exactly as it does for ArrayList. The synchronised mutators don't help; the iterator isn't holding the lock between calls. You still need an external synchronized (v) { ... } for safe iteration.
In short: per-method synchronisation buys very little, and the lock contention it costs is real. ConcurrentHashMap-style fine-grained concurrent collections (CopyOnWriteArrayList, ConcurrentLinkedDeque, etc.) are what modern code reaches for.
The Vector-only API you'll see
A handful of methods exist on Vector and not on List. They're legacy synonyms, kept for backward compatibility:
| Vector method | List equivalent |
|---|---|
addElement(E) | add(E) |
insertElementAt(E, int) | add(int, E) |
removeElement(Object) | remove(Object) |
removeElementAt(int) | remove(int) |
elementAt(int) | get(int) |
setElementAt(E, int) | set(int, E) |
firstElement() / lastElement() | get(0) / get(size()-1) |
elements() | iterator() (returns the older Enumeration) |
capacity() | (no equivalent) |
copyInto(Object[]) | toArray() |
elements() is the one that catches people out — it returns Enumeration<E>, the pre-Iterator traversal interface. If you're reading code that calls elements(), that's a Vector (or Hashtable) tell.
When Vector is acceptable in new code
Honestly, very rarely. Two cases that come up:
- You're maintaining or extending old code that already uses it. Don't churn the surrounding code just to swap
Vector→ArrayList— the gain isn't worth the diff. New code in the same module can useArrayList. - An API requires
Vectorspecifically. A few older Swing classes (JTable'sDefaultTableModel,JList'sDefaultListModelhistorically) take or returnVector. Use what the API demands at the boundary, then convert if you'd rather work with aListelsewhere.
For "I need a thread-safe list," the better choices are:
Collections.synchronizedList(new ArrayList<>())— same per-method locking model, but on the modern class. Still needs external locking for compound operations and iteration.CopyOnWriteArrayList— lock-free reads, safe iteration over a snapshot. Brilliant for many-readers-few-writers (observer lists, event listeners, immutable-ish config caches).
For "I need raw single-threaded list performance," ArrayList. The synchronized overhead on Vector is small but non-zero, and there's no upside if no other thread is involved.
A worked example: ArrayList and Vector side by side
The program below shows the API mirror, the legacy method names, and a tiny demonstration that per-method synchronized is not the same thing as a thread-safe compound operation. Read the synchronisation note at the end — it's the whole point of the chapter.
What the run shows:
ArrayListandVectorare interchangeable through theListinterface — same elements, same equality, same iteration order.- The
Vector-only methods (addElement,firstElement,elements,capacity) are alive and well, which is why you still see them in old code. - The race demonstration is the whole reason
Vectoris "legacy": its synchronisation is the wrong unit. The number of stored1s is bigger than one because the check and the add aren't atomic together.
What's next
The other survivor from the 1.0 era — built on top of Vector and inheriting all of its flaws — is the Stack class. It's the second case study in "useful idea, dated implementation, modern replacement available." That replacement is Deque, which we'll meet two chapters later.
Practice
A teammate writes `if (!vector.contains(x)) vector.add(x);` to safely add `x` only once from multiple threads, on the grounds that `Vector` is thread-safe. What's actually true?