🧩 Methods · Beginner

The call stack in Java

Each call pushes a frame; returning pops it.

🧩 The mysteryA crash report shows 40 lines of "at ...". Most beginners scroll to the bottom. The answer is almost always at the top. Why?

A stack of plates

Every method call pushes a new frame onto the call stack: that call's parameters, locals and the place to return to. Only the top frame runs. When it returns, its frame is popped and the caller resumes exactly where it left off. Last called, first to finish (LIFO).

void main() { a(); }     // [main]
static void a() { b(); } // [main, a]
static void b() { }      // [main, a, b]
🔮 Predict it

Trace the stack

What prints?

static void a() {
    System.out.println("a1"); b();
    System.out.println("a2");
}
static void b() {
    System.out.println("b1"); c();
    System.out.println("b2");
}
static void c() { System.out.println("c"); }
void main() { a(); }
  1. a1 b1 c b2 a2
  2. a1 a2 b1 b2 c
  3. a1 b1 b2 c a2
Show the answer

a pauses at its call to b; b pauses at its call to c. c finishes and is popped, so b resumes and prints b2, then a resumes and prints a2. Callers wait, then pick up right after the call.

🔮 Predict it

Nested calls

What prints?

static int f(int x) {
    System.out.print("f" + x + " ");
    return x + 10;
}
void main() {
    System.out.println(f(f(1)));
}
  1. f1 f11 21
  2. f11 f1 21
  3. 21 f1 f11
Show the answer

The inner f(1) must finish first: it prints f1 and returns 11. Then f(11) prints f11 and returns 21, which println prints last.

Reading a stack trace

A stack trace lists frames newest first, with main at the bottom. The top line is where the exception actually happened; each line below is the caller of the line above. Here divide threw, average called it, and main called average.

java.lang.ArithmeticException: / by zero
    at Main.divide(Main.java:9)
    at Main.average(Main.java:5)
    at Main.main(Main.java:2)
🤔 Think first

Unwinding

divide throws an exception and nobody catches it. What happens to the frames of average and main?

Think about it, then reveal the answer

They're popped one by one as Java looks for a catch. That's called unwinding the stack. If no frame catches it, the program dies and prints the stack trace you just read.

⚠️ The trap

The stack has a ceiling

The call stack has a limited size. Calls that never stop (or go absurdly deep) keep pushing frames until it's full, and the JVM throws StackOverflowError. A plain loop doesn't do this: it reuses the same frame.

static void again() {
    again(); // pushes forever
}
// -> StackOverflowError
💼 In the real world

In real projects

Reading stack traces is a daily debugging skill. Start at the top, then scan down for the first line from your own code (like at com.myshop.Cart...): that's usually where to look. Long traces from frameworks often hide the real cause further down, after "Caused by:".

Key takeaways

  1. Last called, first to finish (LIFO)
  2. Each frame has its own parameters and locals
  3. A stack trace lists frames newest first, with main at the bottom
  4. Too many nested calls overflow the stack

💡 Like a stack of plates: each new call puts a plate on top, and you can only take the top plate off.

🤯 Did you know?

Since Java 9, the StackWalker API lets a running program walk its own call stack, frame by frame, to find out who called it.

Practice questions

What does this print?

static void a() {
    System.out.println("a start");
    b();
    System.out.println("a end");
}
static void b() { System.out.println("b"); }
void main() {
    a();
}
  1. a start a end b
  2. a start b a end
  3. b a start a end
Check your answer

a start b a end. a pauses at the call to b. b's frame runs and is popped, then a resumes right after the call and prints a end.

What does this print?

static int f(int x) {
    System.out.print("f" + x + " ");
    return x * 2;
}
void main() {
    System.out.println(f(f(1)));
}
  1. f2 f1 4
  2. f1 f2 4
  3. 4 f1 f2
  4. f1 4
Check your answer

f1 f2 4. The inner f(1) must finish first: it prints f1 and returns 2. Then f(2) prints f2 and returns 4, which println prints last.

Next: a method that calls ITSELF. Sounds like an infinite mirror, so how does it ever stop?