What is a stack trace?
Stack trace:
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:
java.lang.RuntimeException: Error!- exception type (RuntimeException) and message (Error!). This is the most important line: it says WHAT happenedat Main.method2(Main.java:12)- error occurred in methodmethod2of classMain, fileMain.java, line 12. This is the stack frame (frame) that was active at the time of the errorat Main.method1(Main.java:8)-method2was called frommethod1at line 8. This is the previous stack frameat Main.main(Main.java:4)-method1was called frommainat 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
NotFoundExceptioninstead of returningOptional.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
NumberFormatExceptionwhen 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
Classobjects, 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 ofjstackfor 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 thangetStackTrace()- Flag
-XX:-OmitStackTraceInFastThrowdisables 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 asreturn thisto skip (but lose info) - How does StackWalker work? - Lazy stack reading as stream, can skip frames, access to
Classobjects 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]]