1. Getting a stack trace

Getting a stack trace

The Java programming language offers many ways for a programmer to get information about what is happening in a program. And not just words.

For example, after C++ programs are compiled, they become one large file full of machine code, and all that is available to a programmer at runtime is the address of the block of memory that contains the machine code currently being executed. Not a lot, let's say.

But for Java, even after a program is compiled, classes remain classes, methods and variables don't disappear, and the programmer has many ways to get information about what is happening in the program.

Stack trace

For example, at point in a program's execution, you can find out the class and name of the method currently being executed. And not just one method — you can get information about the entire chain of method calls from the current method back to the main() method.

A list that consists of the current method, and the method that invoked it, and method that called that one, etc. is called a stack trace. You can get it with this statement:

StackTraceElement[] methods = Thread.currentThread().getStackTrace();

You can also write it as two lines:

Thread current = Thread.currentThread();
StackTraceElement[] methods = current.getStackTrace();

The static currentThread() method of the Thread class returns a reference to a Thread object, which contains information about the current thread, i.e. the current thread of execution. You will learn more about threads in Levels 17 and 18 of the Java Core quest.

This Thread object has a getStackTrace() method, which returns an array of StackTraceElement objects, each of which contains information about a method. Taken together, all these elements form a stack trace.

Example:

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

   public static void test()
   {
      Thread current = Thread.currentThread();
      StackTraceElement[] methods = current.getStackTrace();

      for(var info: methods)
         System.out.println(info);
   }
}
Console output
java.base/java.lang.Thread.getStackTrace(Thread.java:1606)
Main.test(Main.java:11)
Main.main(Main.java:5)

As we can see in the example's console output, the getStackTrace() method returned an array of three elements:

  • getStackTrace() method of the Thread class
  • test() method of the Main class
  • main() method of the Main class

From this stack trace, we can conclude that:

  • The Thread.getStackTrace() method was called by the Main.test() method on line 11 of the Main.java file
  • The Main.test() method was called by the Main.main() method on line 5 of the Main.java file
  • Nobody called the Main.main() method — this is the first method in the chain of calls.

By the way, only some of the available information was displayed on the screen. Everything else can be obtained directly from the StackTraceElement object



2. StackTraceElement

As its name suggests, the StackTraceElement class was created to store information about a stack trace element, i.e. one method in the stack trace.

This class has the following instance methods:

Method Description
String getClassName()
Returns the name of the class
String getMethodName()
Returns the name of the method
String getFileName()
Returns the name of the file (one file can contain multiple classes)
int getLineNumber()
Returns the line number in the file where the method was called
String getModuleName()
Returns the name of the module (this can be null)
String getModuleVersion()
Returns the version of the module (this can be null)

They can help you get more complete information about the current call stack:

Code Console output Note
public class Main
{
   public static void main(String[] args)
   {
      test();
   }

   public static void test()
   {
      Thread current = Thread.currentThread();
      StackTraceElement[] methods = current.getStackTrace();

      for(StackTraceElement info: methods)
      {
         System.out.println(info.getClassName());
         System.out.println(info.getMethodName());

         System.out.println(info.getFileName());
         System.out.println(info.getLineNumber());

         System.out.println(info.getModuleName());
         System.out.println(info.getModuleVersion());
         System.out.println();
      }
   }
}
java.lang.Thread
getStackTrace
Thread.java
1606
java.base
11.0.2

Main
test
Main.java
11
null
null

Main
main
Main.java
5
null
null
class name
method name
file name
line number
module name
module version

class name
method name
file name
line number
module name
module version

class name
method name
file name
line number
module name
module version


3. Stack

You already know what a stack trace is, but what is a stack (Stack class)?

A stack is a data structure to which you can add elements and from which you can retrieve elements. In doing so, you can only take elements from the end: you first take the last one added, then the second to last one added, etc.

The name stack itself suggests this behavior, like how you would interact with a stack of papers. If you put sheets 1, 2 and 3 in a stack, you have to retrieve them in reverse order: first the third sheet, then the second, and only then the first.

Java even has a special Stack collection class with the same name and behavior. This class shares a lot of behaviors with ArrayList and LinkedList. But it also has methods that implement stack behavior:

Methods Description
T push(T obj)
Adds the obj element to the top of the stack
T pop()
Takes the element from the top of the stack (the stack depth decreases)
T peek()
Returns the item at the top of the stack (the stack does not change)
boolean empty()
Checks whether the collection is empty
int search(Object obj)
Searches for an object in the collection and returns its index

Example:

Code Stack contents (the top of the stack is on the right)
Stack<Integer> stack = new Stack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
int x = stack.pop();
stack.push(4);
int y = stack.peek();
stack.pop();
stack.pop();

[1]
[1, 2]
[1, 2, 3]
[1, 2]
[1, 2, 4]
[1, 2, 4]
[1, 2]
[1]

Stacks are used quite often in programming. So this is a useful collection.



4. Displaying a stack trace during exception handling

Why is a list of method calls called a stack trace? Because if you think of the list of methods as a stack of sheets of paper with method names, then when you call the next method, you add a sheet with that method's name to the stack. And the next sheet of paper goes on top of that, and so on.

When a method ends, the sheet at the top of the stack is removed. You cannot remove a sheet from the middle of the stack without removing all the sheets above it. Similarly, you cannot terminate a method in the middle of a chain of calls without terminating all the methods that it has called.

Exceptions

Another interesting use for stacks is during exception handling.

When an error occurs in a program and an exception is thrown, the exception contains the current stack tracean array consisting of a list of methods starting, from the main method and ending with the method where the error occurred. There's even the line where the exception was thrown!

This stack trace is stored inside the exception and can be easily retrieved from it using the following method: StackTraceElement[] getStackTrace()

Example:

Code Note
try
{
   // An exception may occur here
}
catch(Exception e)
{
   StackTraceElement[] methods = e.getStackTrace()
}




Catch the exception

Get the stack trace that existed when the error occurred.

This is a method of the Throwable class, so all its descendants (i.e. all exceptions) have the getStackTrace() method. Super convenient, huh?

Display the exception's stack trace

By the way, the Throwable class has another method for working with stack traces, a method that displays all the stack trace information stored inside the exception. It is called printStackTrace().

Quite conveniently, you can call it on any exception.

Example:

Code
try
{
   // An exception may occur here
}
catch(Exception e)
{
   e.printStackTrace();
}
Console output
java.base/java.lang.Thread.getStackTrace(Thread.java:1606)
Main.test(Main.java:11)
Main.main(Main.java:5)