Java Vector

In the last lesson you learned about Java LinkedList. Now we meet an older cousin of ArrayList called the Vector. It works almost exactly like an ArrayList but is thread-safe, and that safety comes at a price. It is a class you should be able to read but rarely choose to write.

🤔 The problem Vector tried to solve

Imagine two parts of your program running at the same time. One adds items to a list. The other reads from that same list at that exact moment. This is called concurrency. When two threads touch one list together, things go wrong:

  • One can see a half-finished change.
  • The list can get corrupted.
  • Your program can crash or give wrong answers.

Early Java wanted a list safe for many threads out of the box, so they made Vector. Every method is synchronized, meaning only one thread runs it at a time. The others wait their turn. So two threads can never scramble the list together.

The catch is the waiting. That safety has a real cost, and for almost every modern program there is a better option.

🧩 Vector is a List, just like ArrayList

A Vector implements the same List interface as ArrayList. So all the methods you know work here too. Import it, create it with a type, and use add, get, set, remove, and size.

import java.util.Vector;
public class Main {
public static void main(String[] args) {
Vector<String> fruits = new Vector<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println(fruits.get(1)); // ✅ same get as ArrayList
fruits.set(0, "Apricot"); // replace index 0
fruits.remove("Cherry"); // remove by value
System.out.println("Size: " + fruits.size());
System.out.println(fruits);
}
}

What each line does:

  • add("Apple") puts a new item at the back, like ArrayList.
  • get(1) returns the second item, Banana.
  • set(0, "Apricot") replaces the item at index 0.
  • remove("Cherry") deletes the first match by value.
  • size() reports how many items remain.

This is identical to ArrayList code. Only the type name changed. That is the point of the List interface.

Output

Banana
Size: 2
[Apricot, Banana]

📜 The legacy methods

Vector is older than the Collections framework, so it carries legacy methods with long, old-style names. Legacy means “kept around from the old days for backward compatibility”. They do the same jobs as the modern methods, with different names.

The legacy methods you will run into:

  • addElement(x) adds an item to the end, same as add(x).
  • elementAt(i) reads the item at an index, same as get(i).
  • firstElement() and lastElement() read the first and last items.
  • removeElement(x) removes by value, same as remove(x).
  • elementCount is the old field behind size().

This example mixes old names with modern ones so you can see they do the same work.

import java.util.Vector;
public class Main {
public static void main(String[] args) {
Vector<String> names = new Vector<>();
names.addElement("Alex"); // ✅ legacy way to add
names.addElement("Riya");
names.add("Arjun"); // ✅ modern way, same result
System.out.println(names.elementAt(0)); // legacy get
System.out.println(names.get(2)); // modern get
System.out.println("First: " + names.firstElement());
System.out.println("Last: " + names.lastElement());
}
}

The walkthrough:

  • addElement and add both append to the end.
  • elementAt(0) and get(2) both read by index, just with different names.
  • firstElement and lastElement are shortcuts for the two ends.

Prefer the modern names. The legacy ones still work, but add and get match every other List, so your code reads consistently. The legacy names exist mainly so very old code keeps compiling.

Output

Alex
Arjun
First: Alex
Last: Riya

🔒 What “synchronized” really buys you

Every public method on Vector is synchronized. So when one thread is inside add, no other thread can enter add, get, remove, or any method. They line up and wait, which stops two threads from corrupting the list at once.

This example adds items from the main thread. The synchronization is invisible because only one thread runs, but it happens on every call.

import java.util.Vector;
public class Main {
public static void main(String[] args) {
Vector<Integer> scores = new Vector<>();
// Every add() below locks the Vector, then unlocks it.
for (int i = 1; i <= 5; i++) {
scores.add(i * 10);
}
System.out.println(scores);
System.out.println("Total items: " + scores.size());
}
}

Notice the hidden cost:

  • Each add quietly locks the whole Vector, runs, then unlocks it.
  • That lock and unlock happens five times here, once per call.
  • In a single-threaded program, the locking protects nothing. You pay the cost for zero benefit.

That is why Vector fell out of favor. Most code runs on one thread for a given list, so all that locking is wasted work.

Output

[10, 20, 30, 40, 50]
Total items: 5

⚠️ Synchronized methods are not the same as safe

Each Vector method is safe on its own. But putting two methods together is not. A sequence of two calls is a compound operation, and Vector does not protect the gap between them. Look at this classic check-then-act bug.

// ❌ Looks safe, but it is not, with many threads
if (!vector.isEmpty()) { // thread A checks: not empty
vector.remove(0); // another thread removed it first!
}

The danger:

  • Thread A calls isEmpty() and sees one item.
  • Before A runs the next line, thread B removes that item.
  • A calls remove(0) on an empty Vector and crashes.

Each call locked correctly, but the lock was released between them. “Each method is thread-safe” does not mean “any group of methods is thread-safe”. For grouped steps you need your own lock around the whole block.

Per-method safety is not enough

Vector locks each method, not your logic. Any time you do “check, then act” or “read, then update” across more than one call, you must wrap the whole sequence in your own synchronized block. The built-in locking will not save you there.

⚖️ ArrayList vs Vector

When do you pick Vector over ArrayList? Almost never. They store data the same way. The differences are about locking and age.

Feature ArrayList Vector
Thread-safe by default No Yes (every method synchronized)
Speed in single-thread code Fast (no locking) Slower (locks on every call)
How it grows when full Grows by about half Doubles in size
Age Modern (Java 1.2) Legacy (Java 1.0)
Legacy methods (elementAt, addElement) No Yes
Recommended for new code Yes No

The simple guidance: for single-threaded code, which is most code, use ArrayList. It does the same job without the locking tax. Only think about thread safety when several threads truly share one list, and even then there are better tools than Vector.

🧵 What to use instead for real concurrency

If many threads genuinely share a list, Vector is still not your best choice. Modern Java gives you two cleaner options:

  • Collections.synchronizedList(new ArrayList<>()) wraps an ArrayList in a synchronized shell. It behaves like Vector, but it is the standard modern way and it is clearer about what it does.
  • CopyOnWriteArrayList is built for lists that are read often but written rarely. Reads need no lock at all, so they are very fast. Writes make a fresh copy behind the scenes.

This example shows both modern choices.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Main {
public static void main(String[] args) {
// ✅ synchronized wrapper around a normal ArrayList
List<String> safe = Collections.synchronizedList(new ArrayList<>());
safe.add("shared item");
// ✅ great when reads happen far more often than writes
List<String> readHeavy = new CopyOnWriteArrayList<>();
readHeavy.add("config value");
System.out.println(safe);
System.out.println(readHeavy);
}
}

Choosing between them:

  • Use Collections.synchronizedList for a general thread-safe list with mixed reads and writes.
  • Use CopyOnWriteArrayList when threads mostly read and rarely write, like a list of settings.

Both say clearly “this list is for concurrency”. Vector hides that intent and drags its legacy baggage along. In new code, reach for these.

Even these need care for compound steps

The same compound-operation rule applies. Collections.synchronizedList locks each call, not your logic. When you iterate over it, you must still wrap the loop in a synchronized block on the list. The single-call safety does not cover multi-call sequences.

📚 A quick note on Stack

One more class sits right on top of Vector. The Stack class extends Vector. A stack is a “last in, first out” pile. You push an item on top and pop the top off, like a stack of plates.

import java.util.Stack;
public class Main {
public static void main(String[] args) {
Stack<String> plates = new Stack<>();
plates.push("Plate 1");
plates.push("Plate 2");
plates.push("Plate 3");
System.out.println("Top: " + plates.peek()); // look at top
System.out.println("Removed: " + plates.pop()); // take the top off
System.out.println(plates);
}
}

Quick read:

  • push puts a plate on top of the pile.
  • peek looks at the top plate without taking it.
  • pop removes and returns the top plate.

Plate 3 comes off first. That is last in, first out. But because Stack extends Vector, it carries the same locking cost. For new stack code, use ArrayDeque instead. It is faster and built for this job. Stack, like Vector, is mostly legacy.

Prefer ArrayDeque for stacks

For a modern stack, use ArrayDeque with push and pop. It is faster than Stack because it does not lock on every call, and it does not inherit Vector’s old design.

⚠️ Common Mistakes

A few Vector mix-ups show up over and over.

Using Vector by default. “Thread-safe” sounds safer, but most lists live on one thread. So the locking just slows you down for no gain.

// ❌ Vector in single-threaded code: paying for locks you never use
Vector<String> list = new Vector<>();
// ✅ ArrayList: same job, no locking tax
List<String> list2 = new ArrayList<>();

For everyday, single-thread code, ArrayList is the right default.

Assuming “thread-safe” means safe for everything. Each method is safe, but a sequence of methods is not. So check-then-act and read-then-update still need your own synchronized block around the whole sequence.

// ❌ unsafe across threads even though each call is synchronized
if (!vector.isEmpty()) {
vector.remove(0);
}
// ✅ lock the whole sequence yourself
synchronized (vector) {
if (!vector.isEmpty()) {
vector.remove(0);
}
}

Forgetting the import. You need import java.util.Vector; before using it. Leaving it out gives a “cannot find symbol” error.

✅ Best Practices

Build these habits and you will use the right list every time.

  • Default to ArrayList. It fits almost every program and skips the locking cost.
  • Avoid Vector in new code. Know how to read it, but do not reach for it.
  • For real concurrency, use the modern tools. Pick Collections.synchronizedList or CopyOnWriteArrayList, not Vector.
  • Lock compound operations yourself. Wrap “check then act” and “read then update” in your own synchronized block.
  • Use ArrayDeque for stacks. It beats the legacy Stack class for new code.
  • Program to the List type. Declaring List<String> instead of Vector<String> lets you swap the implementation later without touching the rest of your code.

🧩 What You’ve Learned

Nicely done. Let’s recap Vector in plain words.

  • ✅ A Vector is a resizable-array List, just like ArrayList, but every method is synchronized.
  • ✅ It supports the same List methods (add, get, set, remove, size) plus legacy ones like elementAt and addElement.
  • ✅ The synchronization makes it slow in single-thread code, because it locks on every call for no benefit.
  • ✅ Synchronized methods are not enough for compound operations. Group steps still need your own lock.
  • ✅ For real concurrency, prefer Collections.synchronizedList or CopyOnWriteArrayList.
  • Stack extends Vector, so it carries the same cost. Use ArrayDeque for new stack code.
  • ✅ For most everyday work, ArrayList is the better default.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    How is Vector different from ArrayList?

    Why: Vector behaves like ArrayList but synchronizes every method, which makes it thread-safe and slower.

  2. 2

    Why is Vector mostly considered legacy now?

    Why: Most lists run on one thread, so the per-call locking is wasted work.

  3. 3

    Does Vector's synchronization make a check-then-act sequence safe?

    Why: Each method is safe alone, but the gap between two calls is not. Wrap compound steps in your own synchronized block.

  4. 4

    What should you use for a thread-safe list in modern code?

    Why: These are the modern, clearer tools for concurrency. Vector is legacy.

🚀 What’s Next?

Lists let you store items in order and allow duplicates. But sometimes you need a collection that keeps only unique items, with no repeats at all. That is a Set. Let’s learn how the Set interface works and why it is so useful.

Java Set Interface

Share & Connect