Skip to content
Ameen Abozaid
PublishedBEG-014-L05

The call stack

  • Lesson 5 of 8
  • 12 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

In Lesson 4 you learned that every method call gets its own private variables. Where does Java keep them? And when main calls a, which calls b, how does Java remember to come back to a after b finishes, and then back to main?

The answer is the call stack. Picture a pile of plates in a kitchen. Each time a method is called, a fresh plate goes on top; the plate holds that call's parameters and local variables. The cook always works on the top plate. When the method returns, its plate is lifted off and the cook carries on with the plate underneath.

This is one of the most useful mental models in programming. It explains return order, local variables, stack traces in error messages, and, in the next lesson, why recursion works and why it can crash. The lesson builds on Lessons 2 and 4.

Learning Objectives

By the end of this lesson you will be able to:

  • Describe the call stack and what one frame holds.
  • Draw the stack at any point of a program in which methods call other methods.
  • Trace a sequence of calls and returns and predict the order of printed output.
  • Explain why a returning method's variables disappear.
  • Read a stack trace from the bottom up (main) to the top (where things went wrong).
  • State what StackOverflowError means in terms of the stack.

Written Explanation

Frames and the stack

When a method is called, the Java Virtual Machine (JVM) creates a frame for that call. The JVM specification describes a frame as the structure that holds a call's data, including its local variables (parameters included), and says a new frame is created on every method invocation and destroyed when the method completes, normally or by an uncaught exception. Frames are kept on a JVM stack, a stack of frames that is only pushed and popped.

A stack is a last-in, first-out structure, like plates:

  • Push: calling a method puts its frame on top.
  • Pop: when a method returns, its frame is removed from the top.
  • Only the top frame is active. The specification calls it the current frame. Frames below are paused, each waiting at the line where it made a call.

Each thread of a program has its own stack (the spec says the stack is private to the thread). Your programs so far use a single thread, so there is one stack.

The stack as a diagram

Take main calls a, a calls b, b calls c. At the moment c is running, the stack looks like this, growing upwards:

text
 top      +-----------+
          |  c        |   <- running now
          +-----------+
          |  b        |   waiting for c to return
          +-----------+
          |  a        |   waiting for b to return
          +-----------+
 bottom   |  main     |   waiting for a to return
          +-----------+

When c finishes its frame is popped; b is on top again and continues at the line after c();. Then b finishes, a continues, and so on until main ends and the program stops.

Two consequences follow directly from the diagram:

  1. Return order. The most recently called method finishes first. Calls nest like brackets: a(b(c())) style.
  2. Local variables vanish on return. A frame owns its variables. Pop the frame and the variables go with it. This is exactly why count in Lesson 4 started from 0 on each call.

What a frame contains

For this course, a frame holds:

  • the parameters (the copies of the arguments, from Lesson 4),
  • the method's local variables,
  • the place to continue in the caller once this call returns, and room for temporary results.

The JVM specification adds that the size of these parts is fixed when the method is compiled. You will not manage frames yourself; the point is the picture: one frame per active call, each with its own variables.

Stack traces: reading the pile from a crash

When a program crashes, Java prints the stack at that moment. Here is a real run (JDK 21) of a program where main calls average, which calls divide, which divides by zero:

text
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Crash.divide(Crash.java:3)
	at Crash.average(Crash.java:7)
	at Crash.main(Crash.java:11)

Read it top to bottom as the stack from top to bottom: the first at line is the top frame, the method that was running when the problem happened (divide, line 3 of the file). The next line is who called it (average, line 7), and the last is main. The line numbers point to the exact lines in your source. When you debug, start at the top frame, and look down the list to see how you got there. You will meet the error type (ArithmeticException) properly in the module on exceptions; for now, notice that the trace is simply the stack, printed.

How deep can the stack go?

The stack has a limited size. The JVM specification says that if a computation needs a larger stack than is allowed, the JVM throws a StackOverflowError, and the Java API documents that error as thrown when an application recurses too deeply. In the next lesson you will see exactly how an endless chain of calls causes it. Ordinary programs have a handful to a few hundred frames; reaching the limit almost always means a bug.

Examples

Example 1: a, b, c and a live stack dump (simple)

java
public class Abc {
    static void c() {
        System.out.println("c running");
        Thread.dumpStack();
    }

    static void b() {
        System.out.println("b calls c");
        c();
        System.out.println("b done");
    }

    static void a() {
        System.out.println("a calls b");
        b();
        System.out.println("a done");
    }

    public static void main(String[] args) {
        System.out.println("main calls a");
        a();
        System.out.println("main done");
    }
}

Thread.dumpStack() is a standard library method that prints the current stack. Treat it as a debugging tool here. Output (JDK 21):

text
main calls a
a calls b
b calls c
c running
java.lang.Exception: Stack trace
	at java.base/java.lang.Thread.dumpStack(Thread.java:2209)
	at Abc.c(Abc.java:4)
	at Abc.b(Abc.java:9)
	at Abc.a(Abc.java:15)
	at Abc.main(Abc.java:21)
b done
a done
main done

The first block of lines shows the pushes (main to a to b to c). The dump is the stack at the deepest point: the top is dumpStack itself, then c, b, a, main, matching the diagram. (The "Exception" wording is just how that method prints; nothing has gone wrong.) The last three lines show the pops in reverse order: b finishes first, then a, then main.

Example 2: tracing values through frames (practical)

java
public class Calc {
    static int add(int a, int b) {
        return a + b;
    }

    static int calculate(int x) {
        int doubled = add(x, x);
        int result = add(doubled, 1);
        return result;
    }

    public static void main(String[] args) {
        int answer = calculate(5);
        System.out.println(answer);
    }
}

Output: 11.

Trace the stack, with each frame's variables:

StepStack (top first)What happens
1main (answer not yet set)main calls calculate(5)
2calculate (x = 5), mainstarts; calls add(5, 5)
3add (a = 5, b = 5), calculate, mainreturns 10; frame popped
4calculate (x = 5, doubled = 10), maincalls add(10, 1)
5add (a = 10, b = 1), calculate, mainreturns 11; frame popped
6calculate (x = 5, doubled = 10, result = 11), mainreturns 11; frame popped
7main (answer = 11)prints 11

Notice that add appears twice, as two separate frames at different times; each had its own a and b. They never interfere. Also the first add frame is gone by step 4, so its a and b no longer exist.

Example 3: a crash read from the stack (realistic)

A program computes an average: main calls average(10, 0), which calls divide.

java
public class Crash {
    static int divide(int a, int b) {
        return a / b;
    }

    static int average(int total, int count) {
        return divide(total, count);
    }

    public static void main(String[] args) {
        System.out.println(average(10, 0));
    }
}

The real output was the stack trace shown earlier. How would you use it? The top line says where it blew up: divide, line 3. You might first be tempted to fix divide itself, but the line below tells you average passed the 0, and the next tells you main passed count = 0 in the first place. Which method should change depends on the design: perhaps divide should check for zero (Lesson 7 does this in the calculator), or the caller should never send 0. The stack trace gives you the full chain of responsibility, not just the final symptom.

Backend Insight

In a backend service you will read stack traces more than almost anything else. Logs from a failed request contain a trace that lists the chain: a web handler, a service method, a database call. Reading it from the top shows what failed; reading down shows which request path led there. Knowing it is "just the call stack" turns a wall of text into a short list of methods to check.

The stack also explains a design point: each request processed by a server runs in its own thread with its own stack, so local variables of different requests do not mix. And because stack space is limited, a service with an accidental infinite recursion fails with StackOverflowError, typically only for the requests that trigger it, rather than silently consuming everything.

Common Mistakes

Mistake 1: Thinking the caller keeps running while the callee runs

a() does not continue until b() returns. Only the top frame runs. Everything below waits.

Mistake 2: Expecting local variables to survive the return

When a method returns, its frame is removed with all its locals. Only the returned value (and anything the caller stored) lives on.

Mistake 3: Reading a stack trace from the wrong end

The top line is where the problem was detected; the bottom line is where the program started (main). Start at the top and follow the "who called me" chain downward.

Mistake 4: Believing separate calls to the same method share variables

Two calls to add, as in Example 2, are two frames with separate parameters. Even a method that calls itself gets a separate frame for each call (next lesson).

Mistake 5: Treating StackOverflowError as a memory-size problem

It means the chain of calls got too deep. The fix is nearly always to correct the logic that never stops calling, not to buy more RAM.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. What is a frame, and when is one created and removed?

Show solution A1

A1. A frame is the block of memory that holds one method call's parameters and local variables. It is created (pushed on the stack) when the method is called and removed (popped) when the method returns.

A2. main calls x(), x() calls y(). Draw the stack, top first, while y is running.

Show solution A2

A2. Top to bottom: y (running), x (waiting for y), main (waiting for x).

A3. When a method returns, what happens to its local variables?

Show solution A3

A3. They are discarded together with the frame, so they no longer exist.

Level B: Application

B1. Predict the output order:

java
static void one() { System.out.println("one start"); two(); System.out.println("one end"); }
static void two() { System.out.println("two"); }
public static void main(String[] args) {
    System.out.println("main start");
    one();
    System.out.println("main end");
}
Show solution B1

B1.

text
main start
one start
two
one end
main end

two finishes before one can print "one end", because one is paused below it.

B2. Using the Calc program from Example 2 but calling calculate(3), give the final printed value and the stack (top first) at the moment add(doubled, 1) is running, with the values of the variables in each frame.

Show solution B2

B2. calculate(3): doubled is 6, result is 7, so the program prints 7. When add(doubled, 1) runs the stack is: add (a = 6, b = 1) on top, then calculate (x = 3, doubled = 6), then main (answer not yet assigned).

B3. A stack trace reads at Shop.tax(Shop.java:12), at Shop.total(Shop.java:20), at Shop.main(Shop.java:31). Which method was running when the error happened, and who called it?

Show solution B3

B3. tax was running, at line 12 of Shop.java. It was called by total (line 20), which was called by main (line 31).

Level C: Challenge

C1. Write three methods first, second, third where each prints "enter X", calls the next one (the last just prints), and then prints "leave X". Predict the exact output, run it, and compare. Explain in terms of the stack why the "leave" lines come in reverse.

Show solution C1

C1. Expected output:

text
enter first
enter second
enter third
leave third
leave second
leave first

Each "leave" comes when that method's frame is back on top, after the call above it was popped. The last frame pushed is the first one popped.

C2. A teammate says: "I'll make average safer, so I'll just catch the problem inside divide." Using the Crash example, explain why the stack trace shows that the decision might belong higher up, and name two reasonable places to guard against a zero count.

Show solution C2

C2. The trace shows the chain main to average to divide; divide is only where the failure was noticed. A guard could go in average (return a sensible result or signal an error if count == 0) or in main/the input stage (refuse a count of 0 before calling). The best place depends on who is responsible for validating the data; divide alone often cannot decide what a good answer would be.

Mini Challenge

Write a program with four methods, main, start, middle and finish, in a chain. Make finish call Thread.dumpStack(). Before you run it, draw the expected stack on paper (top first). Then run it and compare. Finally, add one System.out.println after each call in start and middle and predict the output order.

Check your work: your drawing matches the trace (top is the library line, then finish, middle, start, main), and your output order has the "after" lines in reverse of the call order.

Summary & Next Lesson

Key takeaways

  • Each method call pushes a frame on the call stack; the frame holds that call's parameters and local variables.
  • Only the top frame runs; the frames below wait at the line where they made a call.
  • When a method returns, its frame is popped and its variables vanish; the caller resumes after the call.
  • A stack trace is the stack printed at a crash: top line is where it failed, last line is main.
  • Too many nested calls exhaust the stack and cause StackOverflowError.

Next lesson

You now have the model needed to understand a method that calls itself. The next lesson, "Recursion basics", writes methods such as factorial that solve a problem by calling themselves on a smaller version, shows why a base case is essential, and connects the missing base case to a stack overflow.

Self-check

  1. What does the call stack hold?
  2. Which frame is running: the top or the bottom one?
  3. What happens to a frame when its method returns?
  4. In a stack trace, which line is where the problem was detected?
  5. What does StackOverflowError mean?
Show answers
  1. One frame per active method call, each with that call's parameters and local variables.
  2. The top one; the frames below are waiting.
  3. It is removed (popped) from the top, and its variables are gone.
  4. The first (top) line.
  5. The calls were nested so deeply that the stack's size limit was exceeded.

References

  • Oracle, The Java Virtual Machine Specification, SE 21, Chapter 2. Verified (sections 2.5.2 and 2.6): each thread has a private stack of frames; a frame is created for each method invocation and destroyed when it completes; only the current (top) frame is active; frames hold local variables; exceeding the allowed stack size causes StackOverflowError.
  • Oracle, StackOverflowError (Java SE 21 API). Verified: thrown when an application recurses too deeply; it is an Error (a VirtualMachineError).
  • Stack trace output in Examples 1 and 3 was produced by running the code with JDK 21 (javac 21.0.10); line numbers in your traces will depend on your own files. The claim that ordinary programs have "a handful to a few hundred frames" is a rough rule of thumb and I did not verify it against a source.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.