If you’re a Java developer, you would often encounter Java stack traces while working on various projects. By default, stack traces are printed to the console when unhandled exceptions are thrown.
In spite of this, it’s easy to understand them only on a superficial level. In this article, you will gain insight into what a Java stack trace is and how you can use it.
What Is a Java Stack Trace?
When it comes to debugging code, stack traces (also called stack backtraces or stack tracebacks) are excellent tools.
To put it in simple words, a stack trace represents the call stack at a given point in time, with each element representing a method execution.
Generally, a stack trace is generated when an Exception is not handled properly in code. (Exceptions inform you when your code has an error.) These may be built-in exceptions, or they may be custom exceptions created by a program or library.
Stack traces contain the type and message of an exception, as well as a list of all method, calls in progress when the exception was thrown.
How to Read a Java Stack Trace?
Let’s examine that stack trace. The first line of the Exception tells us the details:
It’s a good start. The code running when that happened is shown in line 2:
That helps us narrow down the problem, but where is bad method in the code? The answer is on the next line down, which can also be read the same way. So how do we get there? Take a look at the next line. So on, until you get to the last line, which is the main method of the application. Taking a look at the stack trace from bottom to top, you can trace the complete path from the start of your code to the exception.
What Triggered the Stack Trace?
An Exception is usually caused by an explicit throw statement. By checking the file name and line number, you can determine what code caused the exception. This is what it will probably look like:
throw new RuntimeException(“Something has gone wrong, aborting!”);
Code can also throw an Exception without an explicit throw statement, for example you get:
- NullPointerException if obj is null when calling obj.someMethod()
- An exception is thrown if a division by zero occurs in integer arithmetic, for example 1/0 – but not in floating-point arithmetic, 1.0/0.0 returns infinity just fine!
- NullPointerException if a null Integer is unboxed to an int in code like this: Integer a=null; a++;
There are a few other examples in the Java Language Specification, so it’s important to understand that Exceptions can arise without being explicitly thrown.
Dealing with Exceptions Thrown by Libraries
Java’s greatest strength is the variety of libraries available. As most popular libraries are well tested, it is best to check first whether the problem is caused by the way our code uses the library.
Javadoc in many good libraries provides information about what types of exceptions can be thrown and why. When a fraction has a zero denominator, Fraction.getFraction throws an ArithmeticException. It is also clear from the message, but documentation can be very helpful in more complicated or ambiguous situations.
This stack trace can be read by starting at the top with the exception’s type – ArithmeticException and the message The denominator must not be zero.
To determine which code caused the Exception, scan down the stack trace looking for something in the package com.myproject, then scan to the end of the line to find the code (MyProject.java:17).
There will be some code that calls Fraction.getFraction in that line. Here’s a starting point for investigation: What goes into getFraction? From where did it come?
When working on large projects with many libraries, stack traces can be hundreds of lines long, so when you see a big stack trace, practice scanning the list of at … at … at … looking for your own code – it’s a useful skill to develop.
Best Practice: Catching and Rethrowing Exceptions
It is not always necessary to catch and rethrow every Exception, but it makes sense when the layers in your code jump, such as when calling into a library.
Notice that the constructor for MyProjectFooBarException takes two arguments: a message and the Exception that caused it.
In Java, every exception has a cause, and when doing a catch-and-rethrow, you should always set that to help debug errors.
The main method is no longer at the bottom, and the code that first threw an exception is no longer at the top. The pattern is the same when there are multiple stages of catch-and-rethrow: It is to review the sections from first to last looking for your code, then read relevant sections from bottom to top.
The StackTraceElement Class
Stack traces are composed of stack trace elements. The StackTraceElement class was the only way to denote such elements before Java 9.
You can access basic information of a method invocation by utilizing a StackTraceElement object, including details on the class and method that was invoked. Using these straightforward APIs, you can retrieve the data:
- getClassName – Returns the fully qualified class name containing the method invocation
- getMethodName – Returns the method name that contains an invocation
Starting with Java 9, using the getModuleName and getModuleVersion methods, you can obtain data on the containing module of a stack frame .
As a result of the SourceFile and LineNumberTable attributes in the class file, the position of each frame in the source file can also be identified. For debugging purposes, this information is very helpful:
- getFileName – returns the source file name associated with the class containing the method invocation
getLineNumber – returns the line number of the source line containing the execution point
See the Java API documentation for a complete list of methods in the StackTraceElement class.
Also Read: Java Buffer pool in Memory space
Access Stack Traces With the Thread Class
A thread’s stack trace can be obtained by calling the get stack trace method on the thread instance. In this invocation, an array of StackTraceElement is returned, which contains information about the thread’s stack frames.
Access Stack Traces With the Throwable Class
When the program throws a Throwable instance, instead of 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.
This method has the same signature and return value as the Thread class method.
Stack Walker Options
An instance of the Option enum type can be used to determine the information retrieved by a stack walker.
The following is a complete list of its constants:
- RETAIN_CLASS_REFERENCE – retains the Class object in every stack frame during a stack walk
- SHOW_REFLECT_FRAMES – shows all reflection frames
- SHOW_HIDDEN_FRAMES – shows all hidden frames, including reflection frames
If you need more information about the error situation, use Seagence. It gives you the complete information about the defect and if you need, you can even debug the issues on your live system when it occurs next time.
All you need to do is to attach a lightweight runtime java agent when you start your application. So that whenever a defect occurs, with Seagence you can get an alert directly to your inbox.
Upon opening the Alert, you can clearly see that this is the functionality failing and this is the exception stack trace that is causing it.
At the same time, you can also open a failure to find more debug information like query parameters, hostname, thread name and response time, etc. Including the exception stack trace that is causing the failure. You will also find steps to reproduce the defect.
Sounds great right? Then why not try using the Seagence Production Debugger tool to debug the error in a similar way as you would in your IDE without affecting any of the other threads of your application.