Java Vector
Java Vector
java.util.Vector<E> is the thread-safe, synchronised version of ArrayList that Java shipped in version 1.0 — long before the Collections Framework, long before generics, and long before the java.util.concurrent package existed. Every method in Vector acquires the same object-level lock before executing. That single design decision is both its historical strength and its modern liability: it was the safest option available in 1996, and it is one of the least efficient options available today.
Understanding Vector is still required for two reasons: it appears constantly in technical interviews as a contrast to ArrayList, and it is widespread in legacy codebases that predate Java 5.
What Is Java Vector?
Vector<E> is a concrete class in java.util that implements List<E>. Internally it is a resizable array — identical to ArrayList in how it stores and accesses elements. The difference is that every public method in Vector is declared synchronized, which means every call acquires the intrinsic lock on the Vector instance before executing.
java.lang.Iterable<E>
└── java.util.Collection<E>
└── java.util.List<E>
└── java.util.AbstractList<E>
├── java.util.ArrayList<E> ← NOT synchronised
└── java.util.Vector<E> ← THIS CLASS
└── java.util.Stack<E> ← subclass of Vector
KEY FACTS:
Package : java.util
Since : Java 1.0 (predates Collections Framework — Java 1.2)
Implements : List<E>, RandomAccess, Cloneable, Serializable
Backing structure: Object[] elementData (same as ArrayList)
Synchronisation : YES — every public method is synchronized
Null elements : allowed
Duplicates : allowed
Ordering : insertion order (same as ArrayList)
Legacy API : Enumeration<E>, addElement(), elementAt(), removeElement()
Default capacity : 10 elements (same as ArrayList)
Capacity growth : doubles by default (ArrayList grows by 50%)
Status : LEGACY — avoid in new code
Basic Overview — The Full Vector API
VECTOR METHOD REFERENCE:
LIST INTERFACE METHODS (all synchronised in Vector):
add(element) addAll(collection)
add(index, element) addAll(index, collection)
get(index) set(index, element)
remove(index) remove(object)
contains(object) containsAll(collection)
size() isEmpty()
indexOf(object) lastIndexOf(object)
iterator() listIterator()
subList(from, to) toArray()
VECTOR-SPECIFIC LEGACY METHODS (pre-Collections Framework):
addElement(E obj) ← same as add(obj)
insertElementAt(E, int) ← same as add(index, element)
setElementAt(E, int) ← same as set(index, element)
removeElement(Object) ← same as remove(Object), returns boolean
removeElementAt(int) ← same as remove(int index)
removeAllElements() ← same as clear()
elementAt(int index) ← same as get(index)
firstElement() ← same as get(0)
lastElement() ← same as get(size()-1)
elements() ← returns Enumeration<E> (legacy iterator)
copyInto(Object[] array) ← same as toArray(array)
CAPACITY MANAGEMENT (Vector-specific):
capacity() ← current allocated array size (not element count)
ensureCapacity(int min) ← grow to at least min if needed
setSize(int newSize) ← resize logical size (fills with null if growing)
trimToSize() ← shrink array to match current element count
GROWTH STRATEGY:
capacityIncrement > 0 → grows by capacityIncrement each resize
capacityIncrement == 0 → doubles current capacity (default)
ArrayList always grows by 50% (approximately)
new Vector<>() → capacity 10, increment 0 (doubles)
new Vector<>(20) → capacity 20, increment 0 (doubles)
new Vector<>(20, 5) → capacity 20, increment 5 (adds 5 each time)
When to Use Vector
DO NOT USE Vector IN NEW CODE.
The Java documentation and every major Java style guide recommend
against new Vector usage. Here is why:
PROBLEM 1 — Global lock kills concurrency:
Every method in Vector acquires the lock on the Vector object.
This means 100 reader threads calling get() simultaneously
execute one at a time — 99 wait while 1 reads.
CopyOnWriteArrayList reads with NO lock at all.
PROBLEM 2 — Synchronisation does not prevent compound races:
Even with Vector, this code has a race condition:
if (!vector.isEmpty()) { // Thread 1 checks here
E elem = vector.get(0); // Thread 2 removes here — IndexOutOfBoundsException!
}
The two operations are separately atomic but the compound
check-then-act is NOT atomic. External locking still needed.
PROBLEM 3 — Enumeration is obsolete:
vector.elements() returns an Enumeration — a pre-Iterator API
from Java 1.0. Enumeration lacks the remove() operation and is
verbose compared to Iterator or for-each.
WHEN Vector IS ENCOUNTERED:
- Maintaining legacy codebases (pre-Java 5)
- Subclassed by java.util.Stack (which is also legacy)
- Third-party library APIs that predate Collections Framework
MODERN ALTERNATIVES:
Single-threaded List → ArrayList (no lock overhead)
Read-heavy concurrent List → CopyOnWriteArrayList (lock-free reads)
Write-heavy concurrent List → Collections.synchronizedList(new ArrayList<>())
+ explicit synchronized block for iteration
Thread-safe stack → ArrayDeque (push/pop without global lock)
How Vector Works Internally
BACKING STRUCTURE — identical to ArrayList:
protected Object[] elementData; ← the array holding elements
protected int elementCount; ← logical size (number of elements)
protected int capacityIncrement; ← growth step (0 = double)
NOTE: these fields are protected, not private.
Direct subclassing and field access is possible — another legacy design.
SYNCHRONISATION MECHANISM:
Every public method has the synchronized keyword:
public synchronized boolean add(E e) { ... }
public synchronized E get(int index) { ... }
public synchronized E remove(int index) { ... }
public synchronized void clear() { ... }
This means: the intrinsic monitor lock on the Vector INSTANCE
is acquired before every method call and released after.
CONSEQUENCE 1: Only one thread can execute any Vector method at a time.
CONSEQUENCE 2: Reads and writes compete for the same lock — readers
block other readers (unlike ReadWriteLock where reads are concurrent).
CONSEQUENCE 3: Compound operations are still NOT atomic:
if (!vector.isEmpty()) { // lock acquired, released
value = vector.get(0); // lock acquired again — window for race
}
RESIZE BEHAVIOUR:
When elementCount == elementData.length and a new element is added:
if (capacityIncrement > 0)
newCapacity = oldCapacity + capacityIncrement; // fixed increment
else
newCapacity = oldCapacity * 2; // double (default)
ArrayList grows to: oldCapacity + (oldCapacity >> 1) ≈ 1.5× (50%)
Vector doubles: oldCapacity * 2 = 2× (100%)
Vector wastes more memory than ArrayList on average.
Core Operations with Examples
Basic Operations — Same as ArrayList, With Lock Overhead
1// File: VectorBasicsDemo.java
2
3import java.util.Enumeration;
4import java.util.Iterator;
5import java.util.Vector;
6
7public class VectorBasicsDemo {
8
9 public static void main(String[] args) {
10
11 // Modern API (List interface methods) — still synchronised
12 Vector<String> cities = new Vector<>();
13 cities.add("Mumbai");
14 cities.add("Delhi");
15 cities.add("Bengaluru");
16 cities.add("Chennai");
17 cities.add("Hyderabad");
18
19 System.out.println("=== List interface methods (synchronised) ===");
20 System.out.println("Vector : " + cities);
21 System.out.println("size() : " + cities.size());
22 System.out.println("get(2) : " + cities.get(2));
23 System.out.println("contains(Delhi): " + cities.contains("Delhi"));
24
25 cities.set(1, "Pune");
26 System.out.println("After set(1, Pune): " + cities);
27
28 cities.remove("Chennai");
29 System.out.println("After remove(Chennai): " + cities);
30
31 System.out.println();
32
33 // Legacy methods — same operations, older naming
34 System.out.println("=== Legacy Vector methods ===");
35 Vector<Integer> numbers = new Vector<>();
36 numbers.addElement(10); // same as add(10)
37 numbers.addElement(20);
38 numbers.addElement(30);
39 numbers.insertElementAt(15, 1); // same as add(1, 15)
40 System.out.println("After addElement and insertElementAt: " + numbers);
41
42 System.out.println("elementAt(0) : " + numbers.elementAt(0)); // same as get(0)
43 System.out.println("firstElement() : " + numbers.firstElement()); // get(0)
44 System.out.println("lastElement() : " + numbers.lastElement()); // get(size()-1)
45
46 numbers.removeElement(15); // same as remove(Integer.valueOf(15))
47 System.out.println("After removeElement(15): " + numbers);
48
49 numbers.removeElementAt(0); // same as remove(0)
50 System.out.println("After removeElementAt(0): " + numbers);
51
52 System.out.println();
53
54 // Enumeration — legacy iteration (pre-Iterator)
55 System.out.println("=== Enumeration (legacy iteration) ===");
56 Vector<String> frameworks = new Vector<>();
57 frameworks.addElement("Spring"); frameworks.addElement("Hibernate");
58 frameworks.addElement("Struts"); frameworks.addElement("JSF");
59
60 Enumeration<String> enumeration = frameworks.elements();
61 System.out.print("Enumeration: ");
62 while (enumeration.hasMoreElements()) {
63 System.out.print(enumeration.nextElement() + " ");
64 }
65 System.out.println();
66
67 // Modern for-each — works on Vector because it implements Iterable
68 System.out.print("for-each : ");
69 for (String fw : frameworks) {
70 System.out.print(fw + " ");
71 }
72 System.out.println();
73 }
74}Output:
=== List interface methods (synchronised) ===
Vector : [Mumbai, Delhi, Bengaluru, Chennai, Hyderabad]
size() : 5
get(2) : Bengaluru
contains(Delhi): true
After set(1, Pune): [Mumbai, Pune, Bengaluru, Chennai, Hyderabad]
After remove(Chennai): [Mumbai, Pune, Bengaluru, Hyderabad]
=== Legacy Vector methods ===
After addElement and insertElementAt: [10, 15, 20, 30]
elementAt(0) : 10
firstElement() : 10
lastElement() : 30
After removeElement(15): [10, 20, 30]
After removeElementAt(0): [20, 30]
=== Enumeration (legacy iteration) ===
Enumeration: Spring Hibernate Struts JSF
for-each : Spring Hibernate Struts JSF
Capacity Management — Vector-Specific Behaviour
1// File: VectorCapacityDemo.java
2
3import java.util.Vector;
4
5public class VectorCapacityDemo {
6
7 public static void main(String[] args) {
8
9 // Default: capacity=10, capacityIncrement=0 (doubles each resize)
10 Vector<Integer> defaultVector = new Vector<>();
11 System.out.println("=== Default capacity behaviour (doubles) ===");
12 System.out.println("Initial capacity : " + defaultVector.capacity()); // 10
13 for (int i = 1; i <= 15; i++) defaultVector.add(i);
14 System.out.println("After 15 adds, capacity : " + defaultVector.capacity()); // 20 (doubled from 10)
15 System.out.println("After 15 adds, size : " + defaultVector.size());
16
17 System.out.println();
18
19 // Custom: capacity=5, capacityIncrement=3 (grows by 3 each time)
20 Vector<String> fixedIncrement = new Vector<>(5, 3);
21 System.out.println("=== Fixed increment capacity (grows by 3) ===");
22 System.out.println("Initial capacity: " + fixedIncrement.capacity()); // 5
23 fixedIncrement.add("A"); fixedIncrement.add("B"); fixedIncrement.add("C");
24 fixedIncrement.add("D"); fixedIncrement.add("E"); // fills to 5
25 System.out.println("At 5 elements, capacity: " + fixedIncrement.capacity()); // 5
26 fixedIncrement.add("F"); // triggers resize: 5 + 3 = 8
27 System.out.println("After 6th element, capacity: " + fixedIncrement.capacity()); // 8
28
29 System.out.println();
30
31 // trimToSize() — release unused capacity
32 Vector<Double> prices = new Vector<>(50); // pre-allocated for 50
33 for (int i = 0; i < 5; i++) prices.add(100.0 * (i + 1));
34 System.out.println("=== trimToSize() ===");
35 System.out.println("Before trim — capacity: " + prices.capacity() + " size: " + prices.size());
36 prices.trimToSize();
37 System.out.println("After trim — capacity: " + prices.capacity() + " size: " + prices.size());
38
39 System.out.println();
40
41 // setSize() — resize logical size (pads with null or truncates)
42 Vector<String> tags = new Vector<>(List.of("Java","Spring","Kafka"));
43 System.out.println("=== setSize() ===");
44 System.out.println("Before setSize(5): " + tags + " size=" + tags.size());
45 tags.setSize(5); // expands, pads with null
46 System.out.println("After setSize(5) : " + tags + " size=" + tags.size());
47 tags.setSize(2); // truncates
48 System.out.println("After setSize(2) : " + tags + " size=" + tags.size());
49 }
50}Output:
=== Default capacity behaviour (doubles) ===
Initial capacity : 10
After 15 adds, capacity : 20
After 15 adds, size : 15
=== Fixed increment capacity (grows by 3) ===
Initial capacity: 5
At 5 elements, capacity: 5
After 6th element, capacity: 8
=== trimToSize() ===
Before trim — capacity: 50 size: 5
After trim — capacity: 5 size: 5
=== setSize() ===
Before setSize(5): [Java, Spring, Kafka] size=3
After setSize(5) : [Java, Spring, Kafka, null, null] size=5
After setSize(2) : [Java, Spring] size=2
Vector vs ArrayList — The Lock Overhead in Practice
1// File: VectorVsArrayListDemo.java
2
3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.List;
6import java.util.Vector;
7
8public class VectorVsArrayListDemo {
9
10 public static void main(String[] args) {
11
12 final int ITERATIONS = 1_000_000;
13
14 // ---- Single-threaded performance comparison ----
15 System.out.println("=== Single-threaded performance (" + ITERATIONS + " adds) ===");
16
17 Vector<Integer> vector = new Vector<>();
18 long start = System.nanoTime();
19 for (int i = 0; i < ITERATIONS; i++) vector.add(i);
20 long vectorTime = System.nanoTime() - start;
21
22 List<Integer> arrayList = new ArrayList<>();
23 start = System.nanoTime();
24 for (int i = 0; i < ITERATIONS; i++) arrayList.add(i);
25 long alTime = System.nanoTime() - start;
26
27 System.out.printf("Vector add time: %,d ms%n", vectorTime / 1_000_000);
28 System.out.printf("ArrayList add time: %,d ms%n", alTime / 1_000_000);
29 System.out.printf("Vector overhead : %.1fx slower (lock acquisition per call)%n",
30 (double) vectorTime / alTime);
31
32 System.out.println();
33
34 // ---- The critical limitation: compound operations still race ----
35 System.out.println("=== Compound operations are still NOT atomic ===");
36 Vector<String> sharedVector = new Vector<>(List.of("A", "B", "C"));
37
38 // This sequence has a race even with Vector:
39 // Thread 1: calls isEmpty() → false, gets lock, releases
40 // Thread 2: calls clear() → gets lock, clears, releases
41 // Thread 1: calls get(0) → IndexOutOfBoundsException!
42 System.out.println("Vector.isEmpty() and Vector.get() are each synchronised");
43 System.out.println("but the CHECK-THEN-ACT compound is NOT atomic.");
44 System.out.println("External locking still required for compound operations.");
45
46 System.out.println();
47
48 // ---- Correct thread-safe list alternatives ----
49 System.out.println("=== Modern thread-safe alternatives ===");
50
51 // Option 1: synchronizedList — same global-lock pattern as Vector
52 List<String> syncList = Collections.synchronizedList(new ArrayList<>());
53 // Iteration still needs external sync block:
54 // synchronized(syncList) { for (String s : syncList) {...} }
55
56 // Option 2: CopyOnWriteArrayList — lock-free reads, safe iteration
57 List<String> cowList = new java.util.concurrent.CopyOnWriteArrayList<>();
58 // for-each never throws CME — no external sync needed for reads
59
60 System.out.println("Collections.synchronizedList: global lock, same as Vector");
61 System.out.println("CopyOnWriteArrayList : lock-free reads, O(n) writes");
62 System.out.println("Recommendation : neither Vector nor synced wrapper");
63 System.out.println(" for new code — use COWAL or CHM");
64 }
65}Output:
=== Single-threaded performance (1,000,000 adds) ===
Vector add time: 38 ms
ArrayList add time: 12 ms
Vector overhead : 3.2x slower (lock acquisition per call)
=== Compound operations are still NOT atomic ===
Vector.isEmpty() and Vector.get() are each synchronised
but the CHECK-THEN-ACT compound is NOT atomic.
External locking still required for compound operations.
=== Modern thread-safe alternatives ===
Collections.synchronizedList: global lock, same as Vector
CopyOnWriteArrayList : lock-free reads, O(n) writes
Recommendation : neither Vector nor synced wrapper
for new code — use COWAL or CHM
Real-World Example — Migrating Legacy Vector Code to Modern Collections
A Java EE application at a legacy enterprise system used Vector throughout for its service layer. Upgrading it to modern equivalents improves throughput by eliminating unnecessary synchronisation in single-threaded paths and replacing global-lock patterns with purpose-built concurrent collections in multi-threaded paths. The migration follows a clear decision tree.
1// File: LegacyOrderRepository.java (before migration — do NOT use in new code)
2
3import java.util.Enumeration;
4import java.util.Vector;
5
6public class LegacyOrderRepository {
7
8 // LEGACY: Vector for order cache — synchronised on every operation
9 private final Vector<String> orderCache = new Vector<>();
10
11 public void cacheOrder(String orderId) {
12 orderCache.addElement(orderId); // legacy: addElement
13 }
14
15 public boolean isOrderCached(String orderId) {
16 return orderCache.contains(orderId); // synced lookup
17 }
18
19 public String getFirst() {
20 return orderCache.isEmpty() ? null : orderCache.firstElement(); // legacy: firstElement
21 }
22
23 public void printAllOrders() {
24 Enumeration<String> en = orderCache.elements(); // legacy: Enumeration
25 while (en.hasMoreElements()) {
26 System.out.println(" ORDER: " + en.nextElement());
27 }
28 }
29
30 public int getCachedCount() {
31 return orderCache.size();
32 }
33}1// File: MigratedOrderRepository.java (after migration — modern equivalent)
2
3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.List;
6import java.util.concurrent.CopyOnWriteArrayList;
7
8public class MigratedOrderRepository {
9
10 // CASE 1: Single-threaded service → ArrayList (no lock overhead)
11 private final List<String> singleThreadedCache = new ArrayList<>();
12
13 // CASE 2: Multi-threaded, read-heavy → CopyOnWriteArrayList
14 private final List<String> readHeavyCache = new CopyOnWriteArrayList<>();
15
16 // CASE 3: Multi-threaded, balanced reads+writes → synchronizedList + explicit sync
17 private final List<String> balancedCache = Collections.synchronizedList(new ArrayList<>());
18
19 // MIGRATION GUIDE:
20 // Vector.addElement(e) → List.add(e)
21 // Vector.insertElementAt(e,i) → List.add(i, e)
22 // Vector.elementAt(i) → List.get(i)
23 // Vector.firstElement() → List.get(0)
24 // Vector.lastElement() → List.get(list.size() - 1)
25 // Vector.removeElement(obj) → List.remove(obj)
26 // Vector.removeElementAt(i) → List.remove(i)
27 // Vector.removeAllElements() → List.clear()
28 // Vector.setElementAt(e, i) → List.set(i, e)
29 // Vector.copyInto(array) → List.toArray(array)
30 // Vector.elements() → List.iterator() or for-each
31
32 public void demoMigratedPatterns() {
33
34 // Legacy pattern → modern equivalent
35 System.out.println("=== Legacy → Modern migration ===");
36
37 // Before: vector.addElement("ORD-001");
38 // After:
39 singleThreadedCache.add("ORD-001");
40 singleThreadedCache.add("ORD-002");
41 singleThreadedCache.add("ORD-003");
42
43 // Before: vector.firstElement()
44 // After:
45 String first = singleThreadedCache.isEmpty() ? null : singleThreadedCache.get(0);
46 System.out.println("First order: " + first);
47
48 // Before: Enumeration<String> en = vector.elements(); while(en.hasMoreElements()){...}
49 // After:
50 System.out.print("All orders: ");
51 singleThreadedCache.forEach(id -> System.out.print(id + " "));
52 System.out.println();
53
54 // Before: vector.removeAllElements();
55 // After:
56 singleThreadedCache.clear();
57 System.out.println("After clear: size=" + singleThreadedCache.size());
58 }
59
60 public static void main(String[] args) {
61
62 System.out.println("MIGRATION DECISION GUIDE:");
63 System.out.println(" Single-threaded → ArrayList");
64 System.out.println(" Read-heavy multi → CopyOnWriteArrayList");
65 System.out.println(" Balanced multi → synchronizedList(ArrayList)");
66 System.out.println(" Stack semantics → ArrayDeque (push/pop)");
67 System.out.println();
68
69 new MigratedOrderRepository().demoMigratedPatterns();
70
71 System.out.println();
72
73 // Showing the compound-operation safety pattern with synchronizedList
74 List<String> safeList = Collections.synchronizedList(new ArrayList<>());
75 safeList.add("order-A"); safeList.add("order-B");
76
77 System.out.println("=== Correct iteration on synchronizedList ===");
78 synchronized (safeList) { // explicit lock for compound operation
79 for (String order : safeList) {
80 System.out.println(" Processing: " + order);
81 }
82 }
83 }
84}Output:
MIGRATION DECISION GUIDE:
Single-threaded → ArrayList
Read-heavy multi → CopyOnWriteArrayList
Balanced multi → synchronizedList(ArrayList)
Stack semantics → ArrayDeque (push/pop)
=== Legacy → Modern migration ===
First order: ORD-001
All orders: ORD-001 ORD-002 ORD-003
After clear: size=0
=== Correct iteration on synchronizedList ===
Processing: order-A
Processing: order-B
Performance Considerations
| Operation | Vector | ArrayList | CopyOnWriteArrayList |
|---|---|---|---|
| add(element) | O(1) amort + lock | O(1) amort | O(n) — full copy |
| get(index) | O(1) + lock | O(1) | O(1) — lock-free |
| remove(index) | O(n) + lock | O(n) | O(n) — full copy |
| contains(obj) | O(n) + lock | O(n) | O(n) — lock-free |
| Iteration | O(n) + lock per call | O(n) | O(n) — lock-free snapshot |
| Concurrent reads | Serialised | Data race | Fully parallel |
| Concurrent writes | Serialised | Data race | Serialised (O(n) each) |
| Growth strategy | ×2 (doubles) | ×1.5 (50% growth) | ×1 (exact copy) |
| Memory waste | Higher (doubles) | Lower (50%) | Exact size |
Vector's lock overhead in single-threaded code. In a single-threaded context — the majority of service code — every Vector method call acquires and releases a lock that nobody else will ever try to acquire. This adds constant overhead per operation. Benchmarks typically show Vector running 2-4x slower than ArrayList for equivalent single-threaded workloads. The lock acquisition itself is the bottleneck, not the array operation.
Capacity doubling wastes memory. When Vector resizes, it doubles its backing array. ArrayList grows by approximately 50%. A Vector that grew to hold 10,001 elements has a backing array of 16,384 slots. The equivalent ArrayList has a backing array of approximately 15,001 slots. Over time, Vector's growth strategy wastes measurably more memory.
Best Practices
Replace Vector with ArrayList in all new single-threaded code without hesitation. The only reason Vector exists is backward compatibility. The Java documentation itself does not recommend it for new code. Every Vector field in a new class is a synchronisation cost with zero concurrent benefit in single-threaded contexts. IDE refactors make this a one-line change.
When migrating legacy code, map each legacy method to its modern equivalent systematically. addElement() → add(), elementAt() → get(), firstElement() → get(0), lastElement() → get(size()-1), removeAllElements() → clear(), elements() → iterator() or for-each. Work through the class method by method with a find-replace for common patterns. Automated tools like IntelliJ's "Replace Vector with ArrayList" refactoring handle most cases.
For thread-safe list requirements in new code, choose based on read-write ratio. Read-heavy with rare writes: CopyOnWriteArrayList — lock-free reads, O(n) writes, no CME during iteration. Balanced or write-heavy: Collections.synchronizedList(new ArrayList<>()) — global lock, but remember that iteration requires an explicit synchronized block. High concurrency with complex operations: consider a ConcurrentHashMap keyed by index or a purpose-built concurrent data structure.
If Stack semantics are needed (LIFO), use ArrayDeque, not Stack or Vector. java.util.Stack extends Vector, inheriting all its global-lock overhead plus the semantic problems of exposing index-based operations on what should be a pure LIFO structure. ArrayDeque.push(), pop(), and peek() are O(1) amortised, zero synchronisation, and semantically clean.
Common Mistakes
Mistake 1 — Using Vector for Thread Safety Without Understanding Its Limits
1// WRONG assumption — Vector makes compound operations thread-safe
2Vector<String> tasks = new Vector<>(List.of("task1", "task2", "task3"));
3
4// Thread 1: Thread 2:
5// tasks.isEmpty() → false (locked) // waiting
6// tasks.clear() executes here // while Thread 1 was between calls
7// tasks.get(0) → IndexOutOfBoundsException!
8
9// WRONG — Vector's per-method synchronisation does NOT protect this
10if (!tasks.isEmpty()) { // lock acquired and RELEASED
11 String t = tasks.get(0); // another thread cleared between these two lines
12}
13
14// CORRECT — external synchronisation for compound operations
15synchronized (tasks) {
16 if (!tasks.isEmpty()) {
17 String t = tasks.get(0); // safe — no other thread enters this block
18 }
19}Mistake 2 — Using Legacy Enumeration When Iterator or For-Each Is Available
1Vector<String> items = new Vector<>(List.of("A", "B", "C", "D"));
2
3// WRONG — Enumeration is verbose and lacks remove()
4Enumeration<String> en = items.elements();
5while (en.hasMoreElements()) {
6 String item = en.nextElement();
7 // Cannot call en.remove() — Enumeration has no remove method
8}
9
10// CORRECT — use for-each for iteration
11for (String item : items) {
12 System.out.println(item);
13}
14
15// CORRECT — use iterator() when removal is needed during traversal
16java.util.Iterator<String> it = items.iterator();
17while (it.hasNext()) {
18 if ("B".equals(it.next())) it.remove(); // safe removal
19}Mistake 3 — Using Vector's capacityIncrement Without Understanding the Impact
1// WRONG — small capacityIncrement causes frequent resizes
2// For a Vector that will hold thousands of elements, increment=1 means
3// a new array allocation and copy on every single add beyond initial capacity
4Vector<String> badChoice = new Vector<>(10, 1); // grows 1 at a time
5for (int i = 0; i < 1000; i++) {
6 badChoice.add("item-" + i); // 990 individual array copies!
7}
8
9// CORRECT — pre-size when expected count is known
10Vector<String> presized = new Vector<>(1000); // no resize for 1000 elements
11// OR: just use ArrayList which has smarter 50% growth
12java.util.List<String> modern = new java.util.ArrayList<>(1000);Mistake 4 — Declaring New Fields as Vector Instead of List
1// WRONG — binds the implementation to the field type
2// Callers can call Vector-specific methods that break encapsulation
3public class OrderService {
4 private Vector<Order> orders = new Vector<>(); // wrong type for new code
5 public Vector<Order> getOrders() { return orders; } // exposes Vector
6}
7
8// CORRECT — program to the interface, use ArrayList internally
9public class OrderService {
10 private final List<Order> orders = new ArrayList<>(); // interface type
11 public List<Order> getOrders() {
12 return java.util.Collections.unmodifiableList(orders); // safe return
13 }
14}Interview Questions
Q1. What is Vector in Java and why is it considered legacy?
Vector<E> is a List implementation from Java 1.0 that stores elements in a resizable array — identical in structure to ArrayList. Every public method in Vector is declared synchronized, meaning it acquires the object's intrinsic lock before executing. It is considered legacy for three reasons: its synchronisation prevents concurrent reads, adding overhead even in single-threaded code; per-method synchronisation does not protect compound check-then-act operations; and its legacy API (addElement(), elements(), firstElement()) predates the Collections Framework. ArrayList replaces it for single-threaded use, and CopyOnWriteArrayList or Collections.synchronizedList() replace it for concurrent use.
Q2. What is the difference between Vector and ArrayList?
Both are resizable-array List implementations. Vector synchronises every method with a global lock — all reads and writes serialise through the same mutex. ArrayList has no synchronisation — it is faster in single-threaded code but unsafe if shared between threads. Vector doubles its capacity on resize; ArrayList grows by approximately 50%. Vector provides legacy methods (addElement(), elementAt(), elements()) from before the Collections Framework; ArrayList provides only the standard List interface. Vector has been in Java since 1.0; ArrayList was introduced in Java 1.2. For new code, ArrayList is always preferred over Vector.
Q3. Does using Vector guarantee thread safety for all operations?
No — not completely. Individual method calls on Vector are each atomically synchronised, but compound operations are not. if (!vector.isEmpty()) { value = vector.get(0); } is a race condition: another thread can remove all elements between the isEmpty() and get(0) calls, causing IndexOutOfBoundsException. Thread-safe individual method calls do not make sequences of method calls atomic. For compound operations, external synchronized(vector) blocks are still required — the same requirement as Collections.synchronizedList().
Q4. What is the difference between Vector and Collections.synchronizedList()?
Both provide a globally-locked List. Vector synchronises methods as part of its class definition — every concrete method body has synchronized. Collections.synchronizedList() wraps any existing List with a proxy that synchronises each method call on the wrapper object. The functional behaviour is essentially equivalent. The practical difference: synchronizedList() can wrap any List, including ArrayList with its better growth strategy. Both require external synchronisation for iteration. For new code where thread-safe list is genuinely needed, CopyOnWriteArrayList (read-heavy) or an explicit ReentrantLock with ArrayList (write-heavy) are better choices.
Q5. What is the Enumeration interface and how does it relate to Vector?
Enumeration<E> is a pre-Collections Framework (Java 1.0) iteration API with two methods: hasMoreElements() and nextElement(). Vector.elements() returns an Enumeration<E> over the vector's elements. Enumeration lacks the remove() method that Iterator provides and is more verbose. It is the predecessor to Iterator<E> (introduced in Java 1.2). Modern code uses for-each, Iterator, or Stream for traversal. Enumeration still appears in legacy code and some older Java EE APIs.
Q6. How does Vector's capacity growth differ from ArrayList's?
Vector doubles its backing array by default when a resize is needed: newCapacity = oldCapacity × 2. If a capacityIncrement was specified at construction, it adds that fixed amount instead: newCapacity = oldCapacity + capacityIncrement. ArrayList grows by approximately 50%: newCapacity = oldCapacity + (oldCapacity >>> 1). Doubling wastes more memory on average — a Vector that grew from 8 to 16 has 8 empty slots; ArrayList would have approximately 4. Pre-sizing avoids this for both: new Vector<>(expectedSize) or new ArrayList<>(expectedSize).
FAQs
Should I ever use Vector in new Java code?
No. The Java documentation states that ArrayList should be used in place of Vector for non-synchronised use cases. For concurrent use cases, CopyOnWriteArrayList, Collections.synchronizedList(), or a ConcurrentHashMap-backed structure are the appropriate choices. The only reason to write Vector today is when interacting with an API that specifically requires it — which is exceedingly rare.
Is Vector thread-safe?
Each individual method call is thread-safe — the lock prevents concurrent execution of any two methods. But compound operations (check-then-act sequences) are still subject to race conditions unless protected by external synchronized(vector) blocks. Many developers consider this partial thread safety more dangerous than no thread safety because it creates false confidence.
What is the relationship between Vector and Stack?
java.util.Stack extends Vector. It adds five methods: push(item), pop(), peek(), empty(), and search(item). Because it extends Vector, all index-based List operations are inherited — Stack.get(0) and Stack.add(5, item) are both callable, which violates pure stack semantics. For LIFO semantics in new code, use ArrayDeque with push(), pop(), and peek() — no global lock and no leaked index methods.
Does Vector fail-fast like ArrayList during iteration?
Yes. Vector's Iterator (obtained via iterator() or for-each) is fail-fast — it throws ConcurrentModificationException if the vector is structurally modified during iteration outside of the iterator's own remove() method. However, the synchronisation on each method does not prevent CME: the iterator uses modCount checking independent of the lock. CopyOnWriteArrayList is the correct choice for CME-free iteration under concurrent modification.
Can I convert a Vector to an ArrayList?
Yes, in one line: List<T> list = new ArrayList<>(vector). This creates a new ArrayList containing all elements in the same order. The two collections are independent after creation — changes to one do not affect the other. Collections.list(vector.elements()) also produces an ArrayList from the legacy Enumeration.
What Java version deprecated Vector?
Vector has never been formally deprecated with the @Deprecated annotation in the JDK. It is informally considered legacy — the Javadoc recommends ArrayList as the preferred alternative since Java 1.2. The class remains fully functional for backward compatibility. Removing it would break millions of lines of existing Java code.
Summary
Vector<E> is Java's original synchronised List — a resizable array where every method call acquires a global lock before executing. It was the correct solution in Java 1.0 when no other thread-safe collection existed. Today, it is slower than ArrayList in single-threaded code (due to lock overhead), slower than CopyOnWriteArrayList in read-heavy multi-threaded code (due to serialised reads), and less safe than it appears (compound operations are still subject to race conditions).
For interviews: know that Vector doubles on resize while ArrayList grows by 50%, explain that per-method synchronisation does not make compound operations atomic, name the legacy API methods and their modern equivalents, and know the three migration paths — ArrayList for single-threaded, CopyOnWriteArrayList for read-heavy concurrent, synchronizedList for write-heavy concurrent. Stack extending Vector is another interview topic — ArrayDeque is the correct modern replacement.
What to Read Next
| Topic | Link |
|---|---|
| How ArrayList provides the same resizable array without Vector's synchronisation overhead | Java ArrayList → |
| How CopyOnWriteArrayList provides thread-safe iteration without global locks | Java LinkedList → |
| How Collections.synchronizedList() wraps any List with the same per-method locking as Vector | Java Collections Framework → |
| How Generics retrofitted type safety onto Vector's originally raw Object[] backing | Java Generics → |
| How Java Iterator replaced Enumeration as the standard traversal API | Java Iterator → |