Java Language Creating and reading stacktraces


Example

When an exception object is created (i.e. when you new it), the Throwable constructor captures information about the context in which the exception was created. Later on, this information can be output in the form of a stacktrace, which can be used to help diagnose the problem that caused the exception in the first place.

Printing a stacktrace

Printing a stacktrace is simply a matter of calling the printStackTrace() method. For example:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // This prints the stacktrace to standard output
    ex.printStackTrace();
}

The printStackTrace() method without arguments will print to the application's standard output; i.e. the current System.out. There are also printStackTrace(PrintStream) and printStackTrace(PrintWriter) overloads that print to a specified Stream or Writer.

Notes:

  1. The stacktrace does not include the details of the exception itself. You can use the toString() method to get those details; e.g.

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. Stacktrace printing should be used sparingly; see Pitfall - Excessive or inappropriate stacktraces . It is often better to use a logging framework, and pass the exception object to be logged.

Understanding a stacktrace

Consider the following simple program consisting of two classes in two files. (We have shown the filenames and added line numbers for illustration purposes.)

File: "Main.java"
1   public class Main {
2       public static void main(String[] args) {
3           new Test().foo();
4       }
5   }

File: "Test.java"
1   class Test {
2       public void foo() {
3           bar();
4       }
5   
6       public int bar() {
7           int a = 1;
8           int b = 0;
9           return a / b;
10      }

When these files are compiled and run, we will get the following output.

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Test.bar(Test.java:9)
        at Test.foo(Test.java:3)
        at Main.main(Main.java:3)

Let us read this one line at a time to figure out what it is telling us.

Line #1 tells us that the thread called "main" has terminated due to an uncaught exception. The full name of the exception is java.lang.ArithmeticException, and the exception message is "/ by zero".

If we look up the javadocs for this exception, it says:

Thrown when an exceptional arithmetic condition has occurred. For example, an integer "divide by zero" throws an instance of this class.

Indeed, the message "/ by zero" is a strong hint that the cause of the exception is that some code has attempted to divide something by zero. But what?

The remaining 3 lines are the stack trace. Each line represents a method (or constructor) call on the call stack, and each one tells us three things:

  • the name of the class and method that was being executed,
  • the source code filename,
  • the source code line number of the statement that was being executed

These lines of a stacktrace are listed with the frame for the current call at the top. The top frame in our example above is in the Test.bar method, and at line 9 of the Test.java file. That is the following line:

    return a / b;

If we look a couple of lines earlier in the file to where b is initialized, it is apparent that b will have the value zero. We can say without any doubt that this is the cause of the exception.

If we needed to go further, we can see from the stacktrace that bar() was called from foo() at line 3 of Test.java, and that foo() was in turn called from Main.main().

Note: The class and method names in the stack frames are the internal names for the classes and methods. You will need to recognize the following unusual cases:

  • A nested or inner class will look like "OuterClass$InnerClass".
  • An anonymous inner class will look like "OuterClass$1", "OuterClass$2", etcetera.
  • When code in a constructor, instance field initializer or an instance initializer block is being executed, the method name will be "".
  • When code in a static field initializer or static initializer block is being executed, the method name will be "".

(In some versions of Java, the stacktrace formatting code will detect and elide repeated stackframe sequences, as can occur when an application fails due to excessive recursion.)

Exception chaining and nested stacktraces

Java SE 1.4

Exception chaining happens when a piece of code catches an exception, and then creates and throws a new one, passing the first exception as the cause. Here is an example:

File: Test,java
1   public class Test {
2      int foo() {
3           return 0 / 0;
4      }
5
6       public Test() {
7           try {
8               foo();
9           } catch (ArithmeticException ex) {
10              throw new RuntimeException("A bad thing happened", ex);
11          }
12      }
13
14      public static void main(String[] args) {
15          new Test();
16      }
17  }

When the above class is compiled and run, we get the following stacktrace:

Exception in thread "main" java.lang.RuntimeException: A bad thing happened
        at Test.<init>(Test.java:10)
        at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
        at Test.foo(Test.java:3)
        at Test.<init>(Test.java:8)
        ... 1 more

The stacktrace starts with the class name, method and call stack for the exception that (in this case) caused the application to crash. This is followed by a "Caused by:" line that reports the cause exception. The class name and message are reported, followed by the cause exception's stack frames. The trace ends with an "... N more" which indicates that the last N frames are the same as for the previous exception.

The "Caused by:" is only included in the output when the primary exception's cause is not null). Exceptions can be chained indefinitely, and in that case the stacktrace can have multiple "Caused by:" traces.

Note: the cause mechanism was only exposed in the Throwable API in Java 1.4.0. Prior to that, exception chaining needed to be implemented by the application using a custom exception field to represent the cause, and a custom printStackTrace method.

Capturing a stacktrace as a String

Sometimes, an application needs to be able to capture a stacktrace as a Java String, so that it can be used for other purposes. The general approach for doing this is to create a temporary OutputStream or Writer that writes to an in-memory buffer and pass that to the printStackTrace(...).

The Apache Commons and Guava libraries provide utility methods for capturing a stacktrace as a String:

org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

com.google.common.base.Throwables.getStackTraceAsString(Throwable)

If you cannot use third party libraries in your code base, then the following method with do the task:

   /**
     * Returns the string representation of the stack trace.
     *
     * @param throwable the throwable
     * @return the string.
     */
    public static String stackTraceToString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

Note that if your intention is to analyze the stacktrace, it is simpler to use getStackTrace() and getCause() than to attempt to parse a stacktrace.