Java Tutorial
🔍

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

Java
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

Java
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

Java
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.

Java
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}
Java
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

OperationVectorArrayListCopyOnWriteArrayList
add(element)O(1) amort + lockO(1) amortO(n) — full copy
get(index)O(1) + lockO(1)O(1) — lock-free
remove(index)O(n) + lockO(n)O(n) — full copy
contains(obj)O(n) + lockO(n)O(n) — lock-free
IterationO(n) + lock per callO(n)O(n) — lock-free snapshot
Concurrent readsSerialisedData raceFully parallel
Concurrent writesSerialisedData raceSerialised (O(n) each)
Growth strategy×2 (doubles)×1.5 (50% growth)×1 (exact copy)
Memory wasteHigher (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

Java
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

Java
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

Java
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

Java
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

TopicLink
How ArrayList provides the same resizable array without Vector's synchronisation overheadJava ArrayList →
How CopyOnWriteArrayList provides thread-safe iteration without global locksJava LinkedList →
How Collections.synchronizedList() wraps any List with the same per-method locking as VectorJava Collections Framework →
How Generics retrofitted type safety onto Vector's originally raw Object[] backingJava Generics →
How Java Iterator replaced Enumeration as the standard traversal APIJava Iterator →
Java Vector | DevStackFlow