What is a stack trace, and how can I use it to debug my application errors? What is a stack trace, and how can I use it to debug my application errors? java java

What is a stack trace, and how can I use it to debug my application errors?


In simple terms, a stack trace is a list of the method calls that the application was in the middle of when an Exception was thrown.

Simple Example

With the example given in the question, we can determine exactly where the exception was thrown in the application. Let's have a look at the stack trace:

Exception in thread "main" java.lang.NullPointerException        at com.example.myproject.Book.getTitle(Book.java:16)        at com.example.myproject.Author.getBookTitles(Author.java:25)        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

This is a very simple stack trace. If we start at the beginning of the list of "at ...", we can tell where our error happened. What we're looking for is the topmost method call that is part of our application. In this case, it's:

at com.example.myproject.Book.getTitle(Book.java:16)

To debug this, we can open up Book.java and look at line 16, which is:

15   public String getTitle() {16      System.out.println(title.toString());17      return title;18   }

This would indicate that something (probably title) is null in the above code.

Example with a chain of exceptions

Sometimes applications will catch an Exception and re-throw it as the cause of another Exception. This typically looks like:

34   public void getBookIds(int id) {35      try {36         book.getId(id);    // this method it throws a NullPointerException on line 2237      } catch (NullPointerException e) {38         throw new IllegalStateException("A book has a null property", e)39      }40   }

This might give you a stack trace that looks like:

Exception in thread "main" java.lang.IllegalStateException: A book has a null property        at com.example.myproject.Author.getBookIds(Author.java:38)        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)Caused by: java.lang.NullPointerException        at com.example.myproject.Book.getId(Book.java:22)        at com.example.myproject.Author.getBookIds(Author.java:36)        ... 1 more

What's different about this one is the "Caused by". Sometimes exceptions will have multiple "Caused by" sections. For these, you typically want to find the "root cause", which will be one of the lowest "Caused by" sections in the stack trace. In our case, it's:

Caused by: java.lang.NullPointerException <-- root cause        at com.example.myproject.Book.getId(Book.java:22) <-- important line

Again, with this exception we'd want to look at line 22 of Book.java to see what might cause the NullPointerException here.

More daunting example with library code

Usually stack traces are much more complex than the two examples above. Here's an example (it's a long one, but demonstrates several levels of chained exceptions):

javax.servlet.ServletException: Something bad happened    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)    at org.mortbay.jetty.Server.handle(Server.java:326)    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)Caused by: com.example.myproject.MyProjectServletException    at com.example.myproject.MyServlet.doPost(MyServlet.java:169)    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)    ... 27 moreCaused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)    at java.lang.reflect.Method.invoke(Method.java:597)    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)    at $Proxy19.save(Unknown Source)    at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)    at com.example.myproject.MyServlet.doPost(MyServlet.java:164)    ... 32 moreCaused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]    at org.hsqldb.jdbc.Util.throwError(Unknown Source)    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)    ... 54 more

In this example, there's a lot more. What we're mostly concerned about is looking for methods that are from our code, which would be anything in the com.example.myproject package. From the second example (above), we'd first want to look down for the root cause, which is:

Caused by: java.sql.SQLException

However, all the method calls under that are library code. So we'll move up to the "Caused by" above it, and in that "Caused by" block, look for the first method call originating from our code, which is:

at com.example.myproject.MyEntityService.save(MyEntityService.java:59)

Like in previous examples, we should look at MyEntityService.java on line 59, because that's where this error originated (this one's a bit obvious what went wrong, since the SQLException states the error, but the debugging procedure is what we're after).


What is a Stacktrace?

A stacktrace is a very helpful debugging tool. It shows the call stack (meaning, the stack of functions that were called up to that point) at the time an uncaught exception was thrown (or the time the stacktrace was generated manually). This is very useful because it doesn't only show you where the error happened, but also how the program ended up in that place of the code.This leads over to the next question:

What is an Exception?

An Exception is what the runtime environment uses to tell you that an error occurred. Popular examples are NullPointerException, IndexOutOfBoundsException or ArithmeticException. Each of these are caused when you try to do something that is not possible. For example, a NullPointerException will be thrown when you try to dereference a Null-object:

Object a = null;a.toString();                 //this line throws a NullPointerExceptionObject[] b = new Object[5];System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,                              //because b is only 5 elements longint ia = 5;int ib = 0;ia = ia/ib;                   //this line throws an  ArithmeticException with the                               //message "/ by 0", because you are trying to                              //divide by 0, which is not possible.

How should I deal with Stacktraces/Exceptions?

At first, find out what is causing the Exception. Try googling the name of the exception to find out what the cause of that exception is. Most of the time it will be caused by incorrect code. In the given examples above, all of the exceptions are caused by incorrect code. So for the NullPointerException example you could make sure that a is never null at that time. You could, for example, initialise a or include a check like this one:

if (a!=null) {    a.toString();}

This way, the offending line is not executed if a==null. Same goes for the other examples.

Sometimes you can't make sure that you don't get an exception. For example, if you are using a network connection in your program, you cannot stop the computer from loosing it's internet connection (e.g. you can't stop the user from disconnecting the computer's network connection). In this case the network library will probably throw an exception. Now you should catch the exception and handle it. This means, in the example with the network connection, you should try to reopen the connection or notify the user or something like that. Also, whenever you use catch, always catch only the exception you want to catch, do not use broad catch statements like catch (Exception e) that would catch all exceptions. This is very important, because otherwise you might accidentally catch the wrong exception and react in the wrong way.

try {    Socket x = new Socket("1.1.1.1", 6789);    x.getInputStream().read()} catch (IOException e) {    System.err.println("Connection could not be established, please try again later!")}

Why should I not use catch (Exception e)?

Let's use a small example to show why you should not just catch all exceptions:

int mult(Integer a,Integer b) {    try {        int result = a/b        return result;    } catch (Exception e) {        System.err.println("Error: Division by zero!");        return 0;    }}

What this code is trying to do is to catch the ArithmeticException caused by a possible division by 0. But it also catches a possible NullPointerException that is thrown if a or b are null. This means, you might get a NullPointerException but you'll treat it as an ArithmeticException and probably do the wrong thing. In the best case you still miss that there was a NullPointerException. Stuff like that makes debugging much harder, so don't do that.

TLDR

  1. Figure out what is the cause of the exception and fix it, so that it doesn't throw the exception at all.
  2. If 1. is not possible, catch the specific exception and handle it.
    • Never just add a try/catch and then just ignore the exception! Don't do that!
    • Never use catch (Exception e), always catch specific Exceptions. That will save you a lot of headaches.


To add on to what Rob has mentioned. Setting break points in your application allows for the step-by-step processing of the stack. This enables the developer to use the debugger to see at what exact point the method is doing something that was unanticipated.

Since Rob has used the NullPointerException (NPE) to illustrate something common, we can help to remove this issue in the following manner:

if we have a method that takes parameters such as: void (String firstName)

In our code we would want to evaluate that firstName contains a value, we would do this like so: if(firstName == null || firstName.equals("")) return;

The above prevents us from using firstName as an unsafe parameter. Therefore by doing null checks before processing we can help to ensure that our code will run properly. To expand on an example that utilizes an object with methods we can look here:

if(dog == null || dog.firstName == null) return;

The above is the proper order to check for nulls, we start with the base object, dog in this case, and then begin walking down the tree of possibilities to make sure everything is valid before processing. If the order were reversed a NPE could potentially be thrown and our program would crash.