Question 15 · Section 7

What is a stack trace?

Stack trace:

Language versions: English Russian Ukrainian

Junior Level

Definition

Stack trace - a list of methods that were called before an exception occurred. It shows the execution path from the error point to the program start.

Stack frame - one element in the call chain. Each time a program calls a method, JVM creates a “frame” in memory that stores: local variables, parameters, return address. Stack trace is simply a list of such frames, from the current method to main().

Example

public class Main {
    public static void main(String[] args) {
        method1();
    }

    static void method1() {
        method2();
    }

    static void method2() {
        throw new RuntimeException("Error!");
    }
}

Stack trace:

java.lang.RuntimeException: Error!
    at Main.method2(Main.java:12)
    at Main.method1(Main.java:8)
    at Main.main(Main.java:4)

How to read

Read top to bottom:

java.lang.RuntimeException: Error!
    at Main.method2(Main.java:12)
    at Main.method1(Main.java:8)
    at Main.main(Main.java:4)

Line by line breakdown:

  1. java.lang.RuntimeException: Error! - exception type (RuntimeException) and message (Error!). This is the most important line: it says WHAT happened
  2. at Main.method2(Main.java:12) - error occurred in method method2 of class Main, file Main.java, line 12. This is the stack frame (frame) that was active at the time of the error
  3. at Main.method1(Main.java:8) - method2 was called from method1 at line 8. This is the previous stack frame
  4. at Main.main(Main.java:4) - method1 was called from main at line 4. This is the root of the stack (entry point)

Analogy: imagine a trail of breadcrumbs. main() left a crumb, called method1(), which left a crumb and called method2(), where everything broke. Stack trace is the path back to the start.

Where you’ll see it

  • In console when running the program
  • In server logs
  • When calling exception.printStackTrace()

When NOT to collect stack trace (Performance Caveat)

Creating a stack trace is an expensive operation (~1-5 microseconds). JVM walks all stack frames and copies them to an array.

Don’t collect stack trace if:

  • Exceptions are used for control flow (anti-pattern) - e.g., throwing NotFoundException instead of returning Optional.empty(). At 1000 requests per second, these milliseconds add up
  • High-load systems - in hot-path (code called thousands of times per second) creating an exception with full stack creates noticeable load on CPU and GC
  • Expected/normal flow - if NumberFormatException when parsing user input is an expected situation, don’t log full stack, message is enough

Alternative for lightweight exceptions: in Java there’s a trick with fillInStackTrace() - you can override it in your exception as return this; to not collect the stack. But then you lose information about the error source.


Middle Level

Internal structure

Stack trace is an array of java.lang.StackTraceElement objects:

StackTraceElement[] elements = Thread.currentThread().getStackTrace();
for (StackTraceElement el : elements) {
    System.out.println(el.getClassName());   // "com.example.MyClass"
    System.out.println(el.getMethodName());  // "myMethod"
    System.out.println(el.getLineNumber());  // 42
    System.out.println(el.getFileName());    // "MyClass.java"
    System.out.println(el.isNativeMethod()); // false
}

Each element contains:

  • File name
  • Class name
  • Method name
  • Line number (if compiled with -g)
  • Native method flag

StackWalker API (Java 9+)

Before Java 9, getStackTrace() always copied the entire stack to an array - expensive.

StackWalker reads stack lazily:

StackWalker.getInstance().forEach(frame ->
    System.out.println(frame.getClassName() + " @ " + frame.getLineNumber())
);

Advantages:

  • Lazy reading (as stream)
  • Skip unnecessary frames (Spring proxies, Hibernate)
  • Access to Class objects, not just string names

Reactive stack traces

In async code (Project Reactor / WebFlux), classic stack trace is useless - it only shows Netty threads internals.

Solution: Hooks.onOperatorDebug() - “glues” logical pieces of stack, but hits performance.


Senior Level

FillInStackTrace Cost

Most time is spent on native stack frame walk (fillInStackTrace). The deeper the method nesting (Spring!), the more expensive exception creation.

Stack Traces in logs

Printing full stack on every 404 - fills disk and chokes I/O.

Best Practice: full stack only for ERROR, for expected business exceptions - only message and cause.

OmitStackTraceInFastThrow

On extremely frequent repetitions of the same exception, JVM (HotSpot) optimizes it and stops filling stack trace:

java.lang.NullPointerException
// No stack!

Flag -XX:-OmitStackTraceInFastThrow disables this optimization for debugging.

Obfuscation

With obfuscation (Proguard/R8), stack trace becomes a.b.c(SourceFile:1). Decryption requires mapping files.

Diagnostics

  • Thread.getAllStackTraces() - snapshot of all threads. “Poor man’s” analog of jstack for deadlock diagnostics.
  • Async Trace ID - in distributed systems use Span ID / Trace ID (OpenTelemetry) to link stacks between microservices.
  • jstack <pid> - full thread dump with monitors
  • JSON structured logs - stack trace as a separate indexable field in ELK

Interview Cheat Sheet

Must know:

  • Stack trace - list of stack frames from error point to main()
  • Each frame stores: class name, method name, line number, file name
  • Creating stack trace - expensive operation (~1-5 microseconds); JVM walks all frames
  • StackWalker (Java 9+) reads stack lazily, more efficient than getStackTrace()
  • Flag -XX:-OmitStackTraceInFastThrow disables JVM optimization that removes stack on frequent repetitions
  • In async code (WebFlux) classic stack trace is useless - shows only framework internals

Frequent follow-up questions:

  • How to read stack trace? - Top to bottom: first line is exception type and message, then - path from error location to entry point
  • When NOT to collect stack trace? - In hot-path (thousands of calls/sec), expected flow (input validation), when exceptions are used for flow control
  • What is fillInStackTrace()? - Native method that walks stack frames; can override as return this to skip (but lose info)
  • How does StackWalker work? - Lazy stack reading as stream, can skip frames, access to Class objects not just strings

Red flags (NOT to say):

  • “Stack trace is free - I always print it” - Stack creation loads CPU and GC, especially in highload
  • “In async code stack trace shows full picture” - In reactive code it shows only Netty/EventLoop internals
  • “OmitStackTraceInFastThrow is a bug” - It’s a HotSpot optimization for frequently repeating exceptions

Related topics:

  • [[16. What does the printStackTrace() method do]]
  • [[17. How to properly log exceptions]]
  • [[19. Why you should not swallow exceptions (catch empty)]]
  • [[13. Can you create custom exceptions]]
  • [[28. What is exception chaining]]