Stack traces are probably one of the most common things you’re regularly running into while working as a Java developer. When unhandled exceptions are thrown, stack traces are simply printed to the console by default.

Nevertheless, it’s easy to only have a surface-level understanding of what these are and how to use them. This article will shed light on the subject.

Simply put, a stack trace is a representation of a call stack at a certain point in time, with each element representing a method invocation. The stack trace contains all invocations from the start of a thread until the point it’s generated. This is usually a position at which an exception takes place.

A stack trace’s textual form like this should look familiar:

Exception in thread "main" java.lang.RuntimeException: A test exception
at com.stackify.stacktrace.StackTraceExample.methodB(StackTraceExample.java:13)
at com.stackify.stacktrace.StackTraceExample.methodA(StackTraceExample.java:9)
at com.stackify.stacktrace.StackTraceExample.main(StackTraceExample.java:5)

When printed out, the generation point shows up first, and method invocations leading to that point are displayed underneath. This printing order makes sense because when an exception occurs, you want to look at the most recent methods first. These methods are likely to contain the root cause of the failure rather than those far away.

The rest of this article will take an in-depth look at stack traces, starting with the StackTraceElement class. Each instance of this class indicates an element in a stack trace.

The Stack Walking API, introduced in Java 9 to provide a more flexible mechanism to traverse call stacks, will be covered as well.

The StackTraceElement Class

A stack trace consists of stack trace elements. Before Java 9, the only way to denote such elements is to use the StackTraceElement class.

Accessible Information

A StackTraceElement object provides you with access to basic data on a method invocation, including the names of the class and method where that invocation occurs. You can retrieve this info using these straightforward APIs:

getClassName – returns the fully qualified name of the class containing the method invocation

getMethodName – returns the name of the method containing the method invocation

Starting with Java 9, you can also obtain data on the containing module of a stack frame – using the getModuleName and getModuleVersion methods.

Thanks to the SourceFile and LineNumberTable attributes in the class file, the corresponding position of a frame in the source file is identifiable as well. This information is very helpful for debugging purposes:

getFileName – returns the name of the source file associated with the class containing the method invocation

getLineNumber – returns the line number of the source line containing the execution point

Accessing Stack Traces With the Thread Class

You can obtain a stack trace from a thread – by calling the getStackTrace method on that Thread instance. This invocation returns an array of StackTraceElement, from which details about stack frames of the thread can be extracted.

The following are two methods of the StackElementExample class. One of them calls the other, hence both become part of the same call stack:

Accessing Stack Traces With the Throwable Class

When the program throws a Throwable instance, instead of simply printing the stack trace on the console or logging it, you can obtain an array of StackTraceElement objects by calling the getStackTrace method on that instance. The signature and the return value of this method are the same as those of the method in the Thread class you have gone through.

Here are two methods featuring the throwing and handling of a Throwable object:

If you were to change the body of the catch block in methodC to a trivial handling...

t.printStackTrace();

...you would see the textual representation of the stack trace:

java.lang.Throwable: A test exception
at com.stackify.stacktrace.StackElementExample.methodD(StackElementExample.java:23)
at com.stackify.stacktrace.StackElementExample.methodC(StackElementExample.java:15)
at com.stackify.stacktrace.StackElementExampleTest
.whenElementOneIsReadUsingThrowable_thenMethodCatchingThrowableIsObtained(StackElementExampleTest.java:34)
...

As you can see, the text output reflects the StackTraceElement array.

The Stack Walking API

One of the prominent features of Java 9 is the Stack Walking API. This section will go over the driving forces behind the introduction of this API, and how to use it to traverse stack traces.

Drawbacks of StackStraceElement

A StackTraceElement object provides more information than a single line in the textual representation of a stack trace. However, each piece of data – such an object stores – is still in a simple form: a String or a primitive value; it doesn’t reference a Class object. Consequently, it’s not easy to use information from a stack trace in the program.

Another problem with the old way of retrieving stack traces is that you cannot ignore frames that you don’t need. On the other hand, you may lose useful elements as the JVM may skip some frames for the performance. In the end, it’s possible to have elements you don’t want and don’t have some you actually need.

The Stack Walking API to the Rescue

The Stack Walking API provides a flexible mechanism to traverse and extract information from call stacks, allowing you to filter, then access frames, in a lazy manner. This API works around the StackWalker class, which encloses two inner types: StackFrame and Option.

Stack Frames

An instance of the StackFrame interface represents an individual frame in a stack, much like what a StackTraceElement object does. As you’d expect, this interface defines a number of APIs, similar to those in the StackTraceElement class, e.g. getMethodName or getLineNumber.

And, if you need to, you can convert a StackFrame object to StackTraceElement by calling the method toStackTraceElement.

However, there is an important API that makes StackFrame a better choice than StackTraceElement – namely getDeclaringClass. This method returns a Class instance, enabling you to perform more complex operations than what you could do with a simple class name. However, do note this is only applicable if the stack walker is set up to retain Class objects.

The next subsection will go over the options you can set for such a stack walker.

Stack Walker Options

Instances of the Option enum type can be used to determine the information retrieved by a stack walker.

Here’s a complete list of its constants:

RETAIN_CLASS_REFERENCE – retains the Class object in each stack frame during a stack walk

The StackWalker Class

The StackWalker class is the entry point to the Stack Walking API. This class doesn’t define public constructors; you must use one of the overloading static methods, named getInstance, to create its objects.

You can have a StackWalker with the default configuration by calling getInstance with no arguments. This configuration instructs the stack walker to retain no class references and omit all hidden frames.

You can also pass an Option constant to that method. In case multiple options are provided, they must be wrapped in a Set before being used to construct a stack walker.

The most noticeable method of StackWalker is the walk method. This method applies a Function to the stream of StackFrame objects, starting from the top frame where the invocation of the walk method occurs.

The frame stream is closed when the walk method returns, and it does so for good reason. Since the JVM is free to reorganize the stack for the performance, the result would be inaccurate if you accessed the stream after the walk method completed.

You can also use a derivative of the walk method, namely forEach. This method performs a Consumer on elements of the StackFrame stream.

Notice that the StackWalker class is thread-safe. Multiple threads can share a single StackWalker instance to go through their own stack without causing any concurrency issues.

To illustrate the Stack Walking API, let’s have a look at this simple class:

The returned list consists of frames corresponding to methods whose class has a qualified name starting with com.stackify. This list has two elements, one denotes the method under test, and the other indicates the test method itself.

The last option, SHOW_HIDDEN_FRAMES, can be used to show all hidden frames, including reflection frames. For instance, lambda expressions only show up in the stack trace when applying this option.

Summary

Java gives us many interesting ways to get access to a stack trace; and, starting with Java 9, the natural option is the Stack Walking API.

This is, simply put, significantly more powerful than the older APIs and can lead to highly useful debugging tools, allowing you to capture the call stack at any particular point in time, and get to the root of any problem quickly.