1. Introduction to Stack

A stack is a data structure that follows the Last In, First Out (LIFO) rule. The last item added is the first item removed. Java provides the legacy java.util.Stack<E> class for this pattern, but modern Java documentation recommends using an implementation of the Deque<E> interface for new stack-based code.

Stacks are useful when work must be processed in reverse order. Examples include undo histories, browser back navigation, matching nested brackets, backtracking algorithms, and the call stack used by program execution.

It is important to distinguish the stack data-structure concept from the specific java.util.Stack class. The LIFO concept remains useful; the legacy class has design limitations that make ArrayDeque a better default for many new programs.

2. Understanding LIFO

LIFO means Last In, First Out. A stack exposes one working end, called the top. Items are added to the top and removed from the top.

Imagine stacking a notebook on a pen and the pen on an eraser. The notebook is at the top, so it must be removed first. Only then can the pen be removed, followed by the eraser.

Whiteboard
Loading diagram...


The three core actions express the LIFO contract:

  • Push: add a new item at the top.
  • Pop: remove and return the top item.
  • Peek: inspect the top item without removing it.

A stack is especially useful when the newest unfinished task or decision must be handled first.

3. The java.util.Stack Class

Stack<E> is a generic class from Java’s original collection library. It extends Vector<E>, which means it inherits the vector’s indexed-list behavior and synchronized methods in addition to providing stack-specific operations.

A typical declaration looks like this:


The type argument String means that this stack stores strings. Using generics provides compile-time type checking and reduces the need for casts when retrieving elements.

Although Stack is still available and supported for compatibility, the Java documentation recommends using Deque implementations for stack behavior in new code.

4. The Five Main Stack Methods

MethodPurposeBehavior when empty
push(item)Adds an item to the top and returns itNot applicable
pop()Removes and returns the top itemThrows EmptyStackException
peek()Returns the top item without removing itThrows EmptyStackException
empty()Checks whether the stack has no itemsReturns true
search(object)Returns the 1-based distance from the top, or -1 if absentReturns -1

4.1 push()

push(item) adds the supplied element to the top. The pushed element becomes the next item returned by peek() or pop().

4.2 pop()

pop() removes and returns the top element. Because a pop requires an element to remove, calling it on an empty Stack throws the unchecked EmptyStackException.

4.3 peek()

peek() returns the top element without changing the stack. This is useful when a program needs to inspect the next item before deciding what to do.

4.4 empty()

empty() returns true when no elements are present and false otherwise. Checking before popping is a common defensive pattern when empty state is expected during normal execution.

4.5 search()

search(object) returns the distance from the top using 1-based positions. The top element has distance 1, the element beneath it has distance 2, and a value not found returns -1. This differs from Java list indices, which begin at 0.

5. Handling an Empty Stack

Calling pop() or peek() on an empty Stack throws EmptyStackException. If empty state is a normal possibility, check it before using those methods.


The check prevents the expected empty case from becoming an exception. In concurrent code, however, a check followed by a separate operation is not automatically atomic: another thread could modify the stack between the two calls. This is another reason to choose a collection and synchronization strategy appropriate to the program.

6. Example: Browser Back Navigation

A browser’s back history follows a LIFO-like pattern: the most recently visited page is the first page returned to when the user presses Back.

A simplified history could hold page names, with the current page at the top. Pressing Back removes the current page and reveals the previous entry. Real browsers have more complex navigation rules, including forward history and navigation branches, but the stack concept explains the basic reverse-order behavior.

For new Java code, this logic is generally better expressed with Deque<String> and ArrayDeque<String> than with Stack<String>.

7. The Inheritance Problem: Stack Extends Vector

The most important design limitation of java.util.Stack is its inheritance relationship:


Vector is a list-like collection. Therefore, Stack inherits methods such as indexed insertion, indexed removal, replacement, and access. These operations allow callers to modify elements anywhere in the collection, not just at the top.

A strict stack abstraction should expose only the operations that preserve LIFO behavior. Since Stack inherits a wider API, callers can bypass the push/pop discipline.

For example, code can insert an element at index 0 or replace an element in the middle using inherited Vector methods. Such code compiles, but it undermines the idea that the stack is manipulated only through its top.

Whiteboard
Loading diagram...


This is an example of an abstraction problem: a class inherits more operations than its intended role needs. Composition—containing a list internally rather than extending it—could have prevented those extra operations from becoming part of the stack’s public API.

8. Iteration Order and Stack Semantics

Because Stack inherits Vector’s list behavior, its ordinary iterator follows the underlying list’s iteration order, from the lowest index toward the highest. That is not the same as repeatedly popping elements from the top.

Consequently, printing a Stack through a normal enhanced for-each loop does not necessarily display items in pop order. If a program specifically needs to process elements in LIFO order, it should use stack operations or choose an API whose traversal behavior matches the requirement.

This distinction reinforces the difference between a data structure’s intended rule and the broader operations exposed by its implementation.

9. Synchronization and Its Limitations

Stack inherits synchronized methods from Vector. Each synchronized method call acquires the object’s monitor lock for the duration of that call.

This design has two important limitations:

  • Overhead: a single-threaded program still uses synchronized methods even when no other thread is competing for the lock.
  • Compound operations: separate synchronized calls do not automatically make a multi-step sequence atomic.

For example, a sequence that checks the top with peek() and then conditionally calls pop() consists of multiple calls. Another thread could change the stack between them unless the whole sequence is protected appropriately.

Per-method synchronization is therefore not a complete concurrency strategy for every workflow. If multiple threads share a stack, choose a concurrent deque or apply a deliberate external locking policy.

10. Why ArrayDeque Is Preferred for New Code

The Java documentation recommends using implementations of Deque for stack semantics in preference to java.util.Stack. ArrayDeque is a common default when a program needs an ordinary, non-thread-safe stack.


The push, pop, and peek operations retain the familiar stack style. Unlike Stack, ArrayDeque does not implement List, so callers cannot use indexed list operations to bypass the stack interface. It also avoids the legacy per-method synchronization inherited from Vector.

ArrayDeque is not synchronized. If multiple threads access a shared deque and at least one thread modifies it, the program needs an appropriate concurrency strategy.

11. Choosing a Stack Implementation

RequirementRecommended direction
New single-threaded stackDeque with ArrayDeque
Concurrent access to both ends of a dequeConsider ConcurrentLinkedDeque
Existing API or legacy code explicitly requires StackRetain Stack where compatibility requires it
Multi-step operations must be atomicApply appropriate synchronization around the whole operation or use a suitable concurrent design

A concurrent deque supports concurrent deque operations, but it does not automatically make every arbitrary multi-step algorithm atomic. If correctness depends on several operations acting as one unit, additional coordination may still be necessary.

Whiteboard
Loading diagram...


12. Real-World Uses of the Stack Pattern

12.1 Undo history

Each user action is pushed onto a stack. When Undo is requested, the most recent action is popped and reversed. A redo feature can use a second stack to keep track of actions that were undone.

12.2 Matching brackets and nested expressions

A parser can push each opening bracket onto a stack. When it encounters a closing bracket, it checks the most recent unmatched opening bracket. If a closing bracket appears with nothing to match, the expression is invalid; if opening brackets remain at the end, the expression is incomplete.

12.3 Backtracking

Maze solving, word-search algorithms, and similar problems may record choices and revisit them in reverse order. A stack can hold the unfinished work or decisions that still need to be explored.

12.4 Browser navigation

A simplified browser history can use a stack for Back navigation. A more complete implementation may maintain a separate forward stack, and visiting a new page after going back generally clears the forward history.

12.5 Function calls

Program execution uses a call stack to track active method calls and their associated execution state. This is a runtime mechanism rather than the same thing as creating a java.util.Stack object in application code, but it follows the same LIFO principle.

13. Common Mistakes

  1. Confusing the stack concept with the Stack class. LIFO remains useful even though the legacy class is often not the best implementation.
  2. Calling pop() or peek() on an empty Stack. These methods throw EmptyStackException.
  3. Assuming search() uses zero-based indices. Its result is a 1-based distance from the top.
  4. Assuming ordinary iteration follows pop order. Stack inherits Vector’s index-based iteration.
  5. Using inherited list methods to modify the middle or bottom. This weakens the LIFO abstraction.
  6. Assuming synchronized methods make a sequence atomic. Separate calls can interleave with other threads.
  7. Choosing Stack for new code without considering Deque. The Java documentation recommends Deque implementations for stack behavior.

14. Best Practices

  • Use Deque<E> with ArrayDeque<E> for most new single-threaded stack requirements.
  • Use push, pop, and peek to make the intended LIFO behavior clear.
  • Decide how an empty stack should be handled before calling removal or inspection methods.
  • Do not rely on inherited indexed operations if the program requires strict stack discipline.
  • Use the appropriate concurrent collection or synchronization strategy when multiple threads share the data.
  • Remember that thread-safe individual methods do not necessarily make compound operations thread-safe.
  • Retain java.util.Stack when compatibility with an existing API makes it necessary, rather than introducing it as the default for new code.

15. Quick Revision

  • A stack follows Last In, First Out (LIFO).
  • push adds, pop removes and returns, and peek inspects the top.
  • pop and peek throw EmptyStackException on an empty Stack.
  • empty() checks whether there are no elements.
  • search() returns a 1-based distance from the top, or -1 if absent.
  • java.util.Stack extends Vector, so it inherits list operations that can bypass LIFO discipline.
  • Stack inherits synchronized methods, but separate method calls do not make compound operations atomic.
  • Java documentation recommends Deque implementations for stack behavior in new code.
  • ArrayDeque is a common choice for a non-thread-safe stack.
  • Stacks are useful for undo, parsing, backtracking, and navigation history.

16. Conclusion

The stack is a simple but powerful data structure: the most recently added item is the next one removed. Java’s legacy Stack class provides push, pop, peek, empty, and search operations, but its inheritance from Vector exposes list operations that weaken the abstraction and brings along per-method synchronization.

For new code, Deque with ArrayDeque is generally the clearer choice for single-threaded LIFO behavior. Use concurrent deques or deliberate synchronization when shared concurrent access is required, and keep the legacy Stack class where compatibility demands it.