Java
Use stacks in Java with the legacy Stack class and the modern Deque interface, and understand the JVM call stack that runs every method.
java.util.Stack Class
The original way to make a stack in Java is the java.util.Stack class. It provides the classic methods push, pop, peek, empty, and search, which are easy to read and remember.
import java.util.Stack;
Stack<String> stack = new Stack<>();
stack.push("first");
stack.push("second");
stack.push("third");
String top = stack.peek(); // "third"
String out = stack.pop(); // "third"
boolean empty = stack.empty(); // false
However, Stack extends Vector, which brings two problems. First, every method is synchronized, adding unnecessary overhead for single-threaded code. Second, because it inherits Vector methods like get(), set(), and add(index, element), callers can mutate the stack in the middle, breaking LIFO guarantees.
The stack operations in detail
The Modern Approach: ArrayDeque as a Stack
The recommended way to implement a stack in modern Java is to use the Deque interface with an ArrayDeque implementation. The Deque interface provides push, pop, and peek methods that mirror stack semantics.
import java.util.ArrayDeque;
import java.util.Deque;
Deque<String> stack = new ArrayDeque<>();
stack.push("first");
stack.push("second");
stack.push("third");
String top = stack.peek(); // "third"
String out = stack.pop(); // "third"
ArrayDeque is a resizable array without synchronization, so it is faster in single-threaded programs and does not expose methods that accidentally break LIFO order. It is the idiomatic stack in Java code written today.
How a resizable array-backed stack works
Stack vs Deque Comparison
| Aspect | java.util.Stack | ArrayDeque (Deque) |
|---|---|---|
| Supertype | Extends Vector | Implements Deque |
| Synchronized | Yes (from Vector) | No |
| Random access | Exposed via Vector | Not exposed |
| LIFO guarantee | Can be violated | Enforced |
| Performance | Slower | Faster |
| Modern preference | Legacy | Recommended |
Java stack implementation details
JVM Call Stack and Stack Frames
Every Java thread has its own JVM call stack. When a method is invoked, the JVM pushes a new stack frame onto that stack. When the method returns, the frame is popped.
Each stack frame stores the method's local variables, the operand stack used to compute intermediate values, and a reference to the runtime constant pool of the method's class. Together these let the JVM track exactly where execution is and how to resume when a method completes.
If a thread calls methods more deeply than its stack allows, usually from unbounded recursion, the JVM throws StackOverflowError. The default stack size is platform dependent but often configurable with the -Xss JVM flag, such as -Xss1m for a 1 MB thread stack.
How call stacks manage function execution
Thread Stacks in Java
Each thread in a Java program gets its own call stack, so the stacks of different threads never interfere. This is what makes concurrent execution safe at the level of method-local state: local variables live on the private stack of the thread that called the method.
When you call new Thread().start(), the Java Virtual Machine allocates a fresh stack for that thread with its own stack size limit. Deep recursion in one thread throws StackOverflowError in that thread alone, without crashing other threads.
For worker threads you can configure the stack size with the Thread(Runnable, String, long stackSize) constructor, though the actual size is still dependent on the platform. How the OS manages per-thread stacks
Complete Java Stack Examples
Legacy Stack approach
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
while (!stack.empty()) {
System.out.println(stack.pop()); // 3, 2, 1
}
}
}
Modern Deque approach with generics
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeStackExample {
public static void main(String[] args) {
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
stack.push(3);
while (!stack.isEmpty()) {
System.out.println(stack.pop()); // 3, 2, 1
}
}
}
Using ArrayDeque for a balanced-parentheses check
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
public class Balanced {
private static final Map<Character, Character> PAIRS =
Map.of(')', '(', ']', '[', '}', '{');
static boolean isValid(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (PAIRS.containsKey(c)) {
if (stack.isEmpty() || stack.pop() != PAIRS.get(c)) {
return false;
}
} else {
stack.push(c);
}
}
return stack.isEmpty();
}
}
More stack problems with full solutions
When to Use Each
Use ArrayDeque for nearly all new stack code: it is faster, type-safe, enforces LIFO, and works with the standard Deque interface that many libraries accept.
Use java.util.Stack only when you need its historical thread-safety through synchronization, or when you are working with legacy code that already depends on it. Even then, the Deque interface offers Collections.synchronizedDeque() as a modern way to get a synchronized stack without the Vector legacy.
Compare with other language implementations
Key Takeaway
Prefer ArrayDeque over java.util.Stack. It is faster, safer, enforces LIFO, and is the modern idiom. And remember that every thread already runs on a JVM call stack, so deep recursion throws StackOverflowError before it can corrupt memory.
Frequently Asked Questions
Why is ArrayDeque preferred over java.util.Stack?
java.util.Stack extends Vector, so every method is synchronized, adding overhead for single-threaded use. Its API also exposes non-stack methods like get() and set() that let callers violate LIFO order. ArrayDeque is unsynchronized, faster, and designed specifically for queue and stack use cases through the Deque interface.
How is Stack different from ArrayDeque in Java?
Stack is a synchronized class that extends Vector and historically contains LIFO behavior mixed with random access methods. ArrayDeque is an unsynchronized, resizable array-backed implementation of the Deque interface, which uses addLast, removeLast, peekLast (or the equivalent push, pop, peek names) and is the recommended modern choice for a stack.
What is the JVM call stack and StackOverflowError?
Each Java thread runs on its own JVM call stack, which holds a stack frame for every method invocation. Each frame stores local variables, the operand stack, and the return address. When a thread exceeds its stack limit, usually from unbounded recursion, the JVM throws StackOverflowError.