1. Introduction to Vector

Vector<E> is a legacy resizable-array class from Java’s original java.util library. It stores elements in an array, supports indexed access, and grows when more space is needed. Its role is similar to ArrayList, but Vector synchronizes its public methods.

Vector was introduced in Java 1.0. When the Collections Framework was added in Java 1.2, Vector was adapted to implement the List<E> interface. It therefore works with common collection operations and can be used wherever a List is expected.

Vector remains available for compatibility with older code and APIs. For most new applications, ArrayList is the usual choice when synchronization is unnecessary. When concurrent access must be supported, the appropriate modern option depends on the program’s read and write patterns.

2. What Vector Provides

Vector supports familiar list operations such as:

  • add(element) to append an element.
  • get(index) to retrieve an element.
  • set(index, element) to replace an element.
  • remove(index) or remove(object) to remove by position or value.
  • size() to count stored elements.
  • indexOf(object) to find the first matching index.

Like other lists, Vector preserves element order and permits duplicates. It uses zero-based indexing, so the first element is at index 0.

Vector also retains older methods from before the Collections Framework, including elementAt, addElement, and removeElementAt. These overlap with modern methods such as get, add, and remove. They remain for backward compatibility; new code generally benefits from using the standard collection API consistently.

3. How Vector Stores Elements

Vector uses a resizable array internally. Its implementation maintains an array of element references and a count of how many entries are currently in use.

Two concepts describe its storage:

  • Size: the number of elements stored in the Vector.
  • Capacity: the number of slots in the backing array.

Capacity may exceed size, leaving spare slots for future additions. When the backing array becomes full, Vector allocates a larger array, copies the existing references, and switches to the new storage.

Whiteboard
Loading diagram...


The conceptual structure is similar to ArrayList. The important differences are Vector’s synchronization behavior and its configurable capacity-increment policy.

4. Constructors and Capacity Growth

Vector provides constructors that let code choose its initial storage and, optionally, how the capacity increases.

ConstructorInitial capacityGrowth behavior
new Vector<>()Historically 10Doubles when full under the default policy
new Vector<>(50)50Doubles when full under the default policy
new Vector<>(16, 8)16Adds 8 slots per growth
new Vector<>(16, 0)16Uses the default doubling policy

The second argument in the two-argument constructor is the capacity increment. A positive value means the array grows by that fixed amount when necessary. A value of zero uses the class’s default growth behavior.

This is a legacy flexibility that is rarely needed in modern application code. A very small fixed increment can cause repeated array allocations and copies as the collection grows. For example, growing by ten slots for every ten new elements can result in substantial cumulative copying for a large Vector.

4.1 Why growth strategy matters

When the backing array is full, a resize must copy the existing element references into a new array. With geometric growth such as doubling, these occasional copies are spread across many appends, so append operations have amortized \(O(1)\) complexity.

With a small fixed increment, the list may resize much more often. If a program has a large expected number of elements, provide a sensible initial capacity and avoid an unnecessarily small fixed increment.

5. Understanding capacity() and size()

Vector exposes both size() and capacity(), unlike ArrayList, which does not provide a public capacity accessor.

  • size() returns the number of actual elements.
  • capacity() returns the length of the backing array.

If a Vector has size 2 and capacity 4, only indices 0 and 1 contain list elements. The remaining two slots are reserved storage, not extra list entries.

This distinction matters because valid indexed access is determined by size, not capacity. Calling get(2) when size is 2 is invalid even if the backing array has spare capacity.

6. Synchronization: What It Means

Vector synchronizes its public methods. A synchronized instance method acquires the monitor lock associated with that Vector before executing the method body and releases the lock when the call finishes.

If one thread is inside a synchronized method on a Vector, another thread attempting to enter a synchronized method on the same object must wait for the lock. This serializes those method calls.

Whiteboard
Loading diagram...


The synchronization was intended to make individual operations safe to share across threads. However, synchronization is not free, and one lock around every operation can limit concurrency.

7. Why Vector’s Thread Safety Has Limits

Vector’s synchronization should not be confused with making every multi-step task automatically safe.

7.1 Synchronization overhead

Even in a single-threaded program, synchronized method calls may involve lock-management overhead. As a result, Vector can be slower than ArrayList for similar workloads, although exact performance depends on the JVM and execution environment.

7.2 Contention between threads

Because the methods use the same object monitor, threads can block one another even when they want to access different elements. The locking is coarse-grained rather than independently protecting different parts of the list.

7.3 Compound operations are not automatically atomic

Consider a task that checks whether a list is empty and then reads its last element. Even if each individual call is synchronized, another thread can change the Vector between the calls. The sequence as a whole is not protected by the individual method locks.

When a multi-step operation must be atomic, the whole sequence needs an appropriate synchronization strategy. This is true even when the underlying collection synchronizes individual methods.

Whiteboard
Loading diagram...


The key lesson is that method-level synchronization protects each call’s execution, not necessarily the program-level invariant that spans several calls.

8. Vector Versus ArrayList

AspectVectorArrayList
IntroducedJava 1.0Java 1.2
Internal structureResizable arrayResizable array
Implements ListYesYes
Indexed access\(O(1)\)\(O(1)\)
AppendAmortized \(O(1)\) under geometric growthAmortized \(O(1)\)
SynchronizationPublic methods are synchronizedNot synchronized by default
Capacity policySupports a fixed capacity incrementUses its own growth policy
Legacy methodsRetains older names such as addElementFocuses on collection API methods
Typical modern roleLegacy compatibilityGeneral-purpose default list

The core list operations are similar. The biggest practical distinction is Vector’s per-method synchronization. If locking is not needed, ArrayList avoids that overhead. If thread-safe access is required, a modern synchronized wrapper or a concurrency-oriented collection may better express the actual requirement.

9. Modern Alternatives for Thread-Safe Lists

There is no single best concurrent list for every workload. Two common choices serve different needs.

9.1 Collections.synchronizedList(...)

A synchronized wrapper can be created around a normal list:


The wrapper synchronizes individual method calls. It is useful when a program needs a synchronized list interface while keeping the underlying implementation replaceable.

Iteration requires extra care: the caller must synchronize on the returned wrapper while traversing it, following the API’s documented pattern. The wrapper does not make a series of separate calls atomic automatically; compound operations may still need an external synchronized block.

9.2 CopyOnWriteArrayList

CopyOnWriteArrayList uses a different strategy. A mutating operation creates a new internal array containing the updated elements, while readers can traverse a stable snapshot without taking the same kind of per-read lock.

This can work well when reads and iteration are frequent but updates are rare, such as event-listener collections or configuration data that changes infrequently. Writes are comparatively expensive because copying the array takes time and memory.

RequirementPotential fit
Ordinary list, no shared concurrent accessArrayList
Synchronized method access around a regular listCollections.synchronizedList(...)
Many reads and iterations, rare writesCopyOnWriteArrayList
Existing API specifically requires VectorVector

10. Where Vector Still Appears

Vector has not disappeared because Java places a high value on backward compatibility. It may still be encountered in:

  • Older Java libraries whose public method signatures use Vector.
  • Legacy applications built before the Collections Framework became standard.
  • GUI and table-model APIs that accept Vector-based row data.
  • Stack, which extends Vector and inherits its synchronized methods.

When maintaining old code, avoid replacing Vector blindly. Check the API contracts and whether callers rely on its behavior. In new code without a compatibility constraint, a List implementation such as ArrayList is usually more appropriate.

11. Legacy Methods

Vector retains several methods that predate the modern collection interfaces.

Legacy methodModern-style equivalent
elementAt(index)get(index)
addElement(value)add(value)
removeElementAt(index)remove(index)
removeElement(value)remove(value)
elements()An Enumeration; modern code often prefers Iterator

These methods remain available so older code continues to compile. For new development, using the standard List methods makes code easier to read alongside other collection implementations.

12. Common Mistakes

  1. Assuming synchronized methods make compound workflows atomic. Several calls can still interleave with another thread’s calls.
  2. Assuming Vector is always the best thread-safe list. Its coarse locking can be inefficient and may not match the workload.
  3. Confusing size with capacity. Spare capacity is not accessible as list elements.
  4. Choosing a tiny fixed capacity increment for a large collection. Frequent resizing can create significant copying overhead.
  5. Using legacy method names in new code without a reason. Prefer the common collection API.
  6. Assuming ArrayList and Vector have identical synchronization behavior. Their method-level concurrency guarantees differ.
  7. Using CopyOnWriteArrayList for frequent writes. Repeated array copies can be costly.

13. Best Practices

  • Use ArrayList for ordinary list requirements when external synchronization is not needed.
  • Choose a concurrency strategy based on how often the collection is read, written, and iterated.
  • Use Collections.synchronizedList when a synchronized wrapper fits, and follow its iteration synchronization requirements.
  • Consider CopyOnWriteArrayList for read-heavy, rarely modified collections.
  • Protect multi-step operations as a whole when correctness depends on a sequence of calls.
  • Keep Vector when a legacy API explicitly requires it, but avoid introducing it as the default for new code.
  • Treat benchmark results as environment-dependent rather than assuming a fixed performance ratio.

14. Quick Revision

  • Vector<E> is a legacy resizable-array implementation of List<E>.
  • It synchronizes its public methods.
  • It supports indexed access and stores size separately from capacity.
  • Its constructors can specify initial capacity and a capacity increment.
  • Geometric growth supports amortized \(O(1)\) appends; small fixed increments may cause repeated copying.
  • Individual synchronized calls do not make multi-call workflows atomic.
  • ArrayList is the normal default when synchronization is unnecessary.
  • Collections.synchronizedList and CopyOnWriteArrayList address different concurrent-use patterns.
  • Vector remains relevant for backward compatibility and legacy APIs.

15. Conclusion

Vector is an important part of Java’s history and remains useful for compatibility with older APIs. It behaves much like a resizable-array list, but synchronizes public methods and retains older methods and capacity controls.

For new development, select a collection based on the actual requirements. Use ArrayList for ordinary list operations, a synchronized wrapper when its locking model fits, and CopyOnWriteArrayList when reads greatly outnumber writes. Keep Vector when compatibility requires it, and remember that per-method synchronization alone does not make a multi-step operation atomic.