Our amazing Android app is just launched on the Android Play Store. Alright, why don’t we take a look at how it is performing? Uh oh, there’s a major crash that occurs when the app is loading and now I have a ton of 1 star reviews. Let’s figure out what’s going on…

We look at the stack trace of the crash and see a series of lines that looks like this:

java.no: com.org.net.java.android.lol.you.can’t.read.this.android(Java.java:1337)
at com.co.uk.ca.keep.at.it.you’re.almost.there(BadCode.java:112)

Before understanding how to read an entire stack trace, we first need to learn how to read a specific line of the stack trace. Take this line as an example:

Let’s look at an example line from a java stacktrace:


Figure 1: Line 13 from figure 2 below

com.crittercism.testapp.errors is the package. It looks like a reverse website URL. Since no two companies can share the same domain name, there will be no package file name collision. This is the reason Android apps don’t need prefixes on their internal classes while those in iOS do.

NullPointerCustomError is the name of the class from which the method was called.

performError is the name of the method that was called.

(NullpointerCustomError.java:10) is the file and line number: This is the most important information of a stack trace. One can think of the file and line numbers in a stack trace like breadcrumbs meant for debugging. They provide information on the execution history of the current thread, and list the names of the classes and methods that were called at the point when the exception occurred.

Now that we understand how to read one line in a stack trace, let’s take a look at a complete crash report we get from an Android app.


Figure 2: Example Android stacktrace from Apteligent

There are 4 main components of a crash report.

Main Components Of Stack Traces

1. Name

The class of the Exception that is thrown.

This particular exception java.lang.NullPointerException is defined in the java libraries and occurs when you try to call a method on a null pointer.

There are 69 java.lang.exceptions, which can be found here.

You can also create your own throwables by extending the Exception class.

Example:

public class myException extends Exception {

}

If we log myException the name on the portal will be com.crittercism.myException.

2. Reason

Why the exception was thrown.

If you want to set a custom reason on your exception, you can either initialize the exception with a reason or override the getMessage() method.

public class myException extends Exception {
    @Override
    public String getMessage(){
        return "This is a test reason.";
    }
}

In this case the reason would come out as “This is a test reason.”.

3. App Version

The version of the app that experienced the crash.

4. Stack Trace

Android stack traces are bottom up just like iOS traces, where thread execution follows the black arrow in the above diagram. The deepest point in the thread execution will typically be higher in the stack trace (line 1 in the above diagram for instance).

The red arrow shows a link between trace 15 and 1. In this case, the NullPointerException is triggered by a call from CustomError.java:50 and is caught and re-thrown as a RuntimeException at CustomError.java:52.

The purple arrow indicates an interesting Java concept; chaining exceptions, which allows the developer to provide insight into one exception by throwing another. For example: you’ve written a method that throws an ArithmeticException because of an attempt to divide by zero. However, the actual cause of the exception was an I/O error which caused the divisor to be zero. Chaining these exceptions will allow for clearer insight into the cause of the exception.

Let’s examine the following code as another example:

public class ExceptionExample {
    public static void divide(){
        try {
            int a = 10/0;
        }
        catch(ArithmeticException e) {
            //Root cause for the exception
            RuntimeException outerException = new RuntimeException();
            outerException.initCause(e);
            Throw outerException;
        }
    }

    public static void main(String args[]){
        try{
            divide();
        }
        catch(Exception e){
            System.out.println(e);
            System.out.println("This exception was caused by: " + e.getCause());
        }
    }
}

In our example code, the divide() method is throwing an ArithmeticException after catching the standard Exception. These chains may make stack traces easier to read by moving exceptions from internal libraries higher up the stack trace, but simultaneously may make it difficult to find one’s own suspect line by moving it higher in the trace.