java.util.Stack<E> is Java's classic last-in-first-out container. The most recent item you push is the first one you pop, which is the same pattern you'd use to model an undo history, a navigation back button, or a parser walking through nested brackets. This lesson covers what a stack is, how Stack's push/pop/peek API works, an inheritance design choice that hurt the class, and why the official Java docs now point you at a different class for new code.
A stack is a one-ended container. You add items at the top, and you remove items from the top. Nothing else. The item that comes out is always the one that went in most recently. That ordering rule has a name: last-in, first-out, abbreviated LIFO.
Consider a small pile of products on a shelf. You stack a notebook on top of a pen, and a pen on top of an eraser. To take the eraser back, you have to lift the notebook, then the pen, then finally the eraser. The shelf gives you no shortcut to reach the bottom item without disturbing the top.
The "top" is the only end the stack reveals to you. Push adds an item on top, pop removes the top item, and peek lets you glance at the top without taking it off. That's the entire LIFO contract.
LIFO is the right shape for any task that has to back out in the reverse order it went forward. Cart edits to undo? Stack. Pages a user has visited in a browsing session? Stack. Function calls the JVM itself manages while your program runs? That's literally called the call stack.
java.util.Stack<E> is a generic class, so you parameterize it with the element type just like ArrayList. The five stack-specific methods are:
| Method | What it does | If the stack is empty |
|---|---|---|
push(E item) | Adds item on top. Returns the item itself. | Works fine. |
pop() | Removes and returns the top item. | Throws EmptyStackException. |
peek() | Returns the top item without removing it. | Throws EmptyStackException. |
empty() | Returns true if the stack has no items. | Returns true. |
search(Object o) | Returns the 1-based distance of o from the top, or -1 if not found. | Returns -1. |
A small program that exercises all five against a cart-edit history:
A few details. peek() left the stack unchanged, which is why the same item was still on top when pop() ran. pop() returned the value that was removed, so you can capture it and act on it in the same step. search is 1-based, not 0-based: the top item is at position 1, the one below it is at 2, and a missing item is -1. That 1-based indexing is a quirk of this class and a frequent off-by-one source.
Stack inherits from Vector, so every push, pop, peek, and search is synchronized. Each call grabs a lock on the stack object. In single-threaded code, this is wasted work on every operation.
Calling pop() or peek() on an empty stack is a common stack bug. Java doesn't return a sentinel like null for the empty case; it throws an unchecked EmptyStackException and the program crashes if no one catches it.
The defensive shape most code uses is to check empty() first and only pop when there's something to remove. That keeps the exception out of normal control flow.
The push/pop/peek triple gives you most of what applications need. A browser back button for a product browsing session, written with Stack<String>. The customer navigates through category pages, then clicks back, then back again. The current page is always on top.
The back helper guards against popping the only remaining entry, because most browsers leave you on the home page rather than navigating to a blank state. The check uses size(), which Stack inherits from Vector, and that's a hint about the next topic.
If you open the source of java.util.Stack, the first line of the class declaration is this:
Stack extends Vector. That's the design choice that haunts the class. Vector is a List, which means it exposes every List method: add(int index, E element), get(int index), set(int index, E element), remove(int index), add(E e), and so on. Stack inherits all of them, which breaks the LIFO contract from the inside.
A LIFO container should only let you touch the top. If a caller can reach in and rewrite the bottom, the "last-in, first-out" property isn't enforced; it's just a hope.
The kind of code the inheritance allows:
Three things happened that a real LIFO container should never have allowed. A new entry was inserted at the bottom. An entry in the middle was deleted by index. A different entry was overwritten in place. The stack still has a "top", but the items below it are no longer the history of what was pushed.
There's nothing illegal about this code. It compiles. It runs. It corrupts the meaning of the stack without warning, which is worse than a compile error, because the bug shows up far from the call that caused it.
The deeper issue is that Stack is iterable from the bottom up, because Vector's iterator walks indices 0 through size - 1. This is why the for loop above printed entries in reverse of the pop order. A LIFO iteration would print top first, but Stack inherits Vector's bottom-first iteration.
The lesson isn't that inheritance is bad. It's that inheriting from a class with a wider interface than you want forces you to inherit operations that don't fit. Stack should have had a Vector inside it, not been a Vector. The classic phrase is "favor composition over inheritance," and java.util.Stack is the cautionary example most Java courses point at.
The second problem Stack inherits from Vector is synchronization. Every method on Vector is synchronized, which means every call acquires the intrinsic lock on the instance. Stack doesn't override these methods to remove the synchronization. Every push, pop, and peek pays the lock cost, whether the calling code is multithreaded or not.
In a single-threaded program, the lock is uncontended, so the cost is small per call. It is not zero. The JVM still has to perform the lock-acquire and lock-release bookkeeping, and the synchronized methods cannot be inlined or optimized as aggressively as an unsynchronized equivalent. Over millions of operations, the overhead is measurable.
The bigger objection is honesty about the contract. If your code is single-threaded, paying for thread safety you don't need is wasted work. If your code is multithreaded, Stack's per-method synchronization isn't enough on its own. Composite operations like "peek and then pop if it matches" need an external lock anyway, because another thread can sneak in between the two calls. So Stack is too synchronized for the single-threaded case and not synchronized enough for the multithreaded case, all at once.
Even uncontended synchronized calls have measurable overhead in tight loops. A few hundred million pushes on Stack will run noticeably slower than the same workload on an unsynchronized deque, even with no contention.
The official Java documentation for java.util.Stack includes a recommendation, right in the class Javadoc, that new code should use Deque implementations for stack semantics:
A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class.
The preferred implementation is java.util.ArrayDeque. It implements Deque, which has push, pop, and peek methods that behave the way you'd want for a stack. It isn't synchronized, so single-threaded code doesn't pay the lock cost. It doesn't extend List, so callers can't bypass the LIFO contract with add(index, element). It iterates from top to bottom, matching the pop order.
The same browser back button, rewritten with ArrayDeque:
The push/pop/peek calls look identical. The behavior is identical. The difference is what's not in the API: no add(int, E), no inherited List machinery, no per-method locking.
Why does java.util.Stack still exist? Removing it would break decades of existing code. The class lives on for backward compatibility. The documentation steers you toward the better option, but the old class is still there for code that already uses it.
The diagram captures the modern recommendation. Pick ArrayDeque for single-threaded LIFO work, pick a concurrent deque for multi-threaded LIFO work, and use Stack only when an existing codebase already uses it and consistency matters more than the upgrade.
The stack idea shows up well beyond Java's Stack class. Once you can spot the LIFO shape, the right container choice is obvious.
Undo for cart edits. Every cart action gets pushed onto a stack. Each undo pops the most recent action and reverses it. If the customer adds the notebook, then increases its quantity, then adds a pen, the undo order reverses each step starting from the pen. Redo, when supported, uses a second stack that holds the actions you've undone.
Parsing nested category expressions. Consider a search box that accepts queries like (electronics AND (laptops OR tablets)) OR books. To check the parentheses match, walk through the string and push every ( onto a stack. For every ), pop the stack. If the stack is empty when you try to pop, there's a stray closing parenthesis. If anything is left on the stack at the end, there's an unmatched opening parenthesis.
The first query is balanced, so every ( was matched by a later ). The second query has one extra (, which is still on the stack at the end, so isBalanced returns false. The example uses ArrayDeque rather than Stack. The push/pop API reads the same way.
Backtracking through a search tree. Any algorithm that explores choices and then reverses them, like a maze solver picking directions or a word search trying letter paths, uses a stack implicitly. Recursive solutions get the stack automatically from the JVM's call stack. Iterative versions usually carry an explicit Deque to hold the unfinished work.
Browser navigation. The back button appeared earlier. The forward button adds a second stack: when you click back, the page you left gets pushed onto the forward stack. When you click forward, you pop from that stack. Visiting a new page from the middle of the history clears the forward stack, which is the same behavior every real browser implements.
The connecting theme across all four uses is that the most recent thing is the next thing to deal with. When the code reads "remember this, I'll come back to it later, and the last one I remembered is the one I want first," a stack is the data structure being described.
10 quizzes