Простыми словами, трассировка стека – это список методов, которые были вызваны до момента, когда в приложении произошло исключение.
Простой случай
В указанном примере мы можем точно определить, когда именно произошло исключение. Рассмотрим трассировку стека:
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)
Это пример очень простой трассировки. Если пойти по списку строк вида «at…» с самого начала, мы можем понять, где произошла ошибка. Мы смотрим на верхний вызов функции. В нашем случае, это:
at com.example.myproject.Book.getTitle(Book.java:16)
Для отладки этого фрагмента открываем Book.java
и смотрим, что находится на строке 16
:
public String getTitle() {
System.out.println(title.toString()); <-- line 16
return title;
}
Это означает то, что в приведенном фрагменте кода какая-то переменная (вероятно, title
) имеет значение null
.
Пример цепочки исключений
Иногда приложения перехватывают исключение и выбрасывают его в виде другого исключения. Обычно это выглядит так:
try {
....
} catch (NullPointerException e) {
throw new IllegalStateException("A book has a null property", e)
}
Трассировка в этом случае может иметь следующий вид:
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:35)
... 1 more
В этом случае разница состоит в атрибуте «Caused by» («Чем вызвано»). Иногда исключения могут иметь несколько секций «Caused by». Обычно необходимо найти исходную причину, которой оказывается в самой последней (нижней) секции «Caused by» трассировки. В нашем случае, это:
Caused by: java.lang.NullPointerException <-- root cause
at com.example.myproject.Book.getId(Book.java:22) <-- important line
Аналогично, при подобном исключении необходимо обратиться к строке 22
книги Book.java
, чтобы узнать, что вызвало данное исключение – NullPointerException
.
Еще один пугающий пример с библиотечным кодом
Как правило, трассировка имеет гораздо более сложный вид, чем в рассмотренных выше случаях. Приведу пример (длинная трассировка, демонстрирующая несколько уровней цепочек исключений):
javax.servlet.ServletException: Произошло что–то ужасное
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 more
Caused 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 more
Caused 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
В этом примере приведен далеко не полный стек вызовов. Что вызывает здесь наибольший интерес, так это поиск функций из нашего кода – из пакета com.example.myproject
. В предыдущем примере мы сначала хотели отыскать «первопричину», а именно:
Caused by: java.sql.SQLException
Однако все вызовы методов в данном случае относятся к библиотечному коду. Поэтому мы перейдем к предыдущей секции «Caused by» и найдем первый вызов метода из нашего кода, а именно:
at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
Аналогично предыдущим примерам, необходимо обратить внимание на MyEntityService.java
, строка 59
: именно здесь появилась ошибка (в данном случае ситуация довольно очевидная, так как об ошибке сообщает SQLException
, но в этом вопросе мы рассматриваем именно процедуру отладки с помощью трассировки).
Перевод ответа: «What is a stack trace, and how can I use it to debug my application errors?» @Rob Hruska
From Wikipedia, the free encyclopedia
«Backtrace» redirects here. For the 2018 film, see Backtrace (film).
In computing, a stack trace (also called stack backtrace[1] or stack traceback[2]) is a report of the active stack frames at a certain point in time during the execution of a program. When a program is run, memory is often dynamically allocated in two places; the stack and the heap. Memory is continuously allocated on a stack but not on a heap, thus reflective of their names. Stack also refers to a programming construct, thus to differentiate it, this stack is referred to as the program’s function call stack. Technically, once a block of memory has been allocated on the stack, it cannot be easily removed as there can be other blocks of memory that were allocated before it. Each time a function is called in a program, a block of memory called an activation record is allocated on top of the call stack. Generally, the activation record stores the function’s arguments and local variables. What exactly it contains and how it’s laid out is determined by the calling convention.
Programmers commonly use stack tracing during interactive and post-mortem debugging. End-users may see a stack trace displayed as part of an error message, which the user can then report to a programmer.
A stack trace allows tracking the sequence of nested functions called — up to the point where the stack trace is generated. In a post-mortem scenario this extends up to the function where the failure occurred (but was not necessarily caused). Sibling calls do not appear in a stack trace.
Language support[edit]
Many programming languages, including Java[3] and C#,[4] have built-in support for retrieving the current stack trace via system calls. Before std::stacktrace
was added in standard library as a container for std::stacktrace_entry
, pre-C++23 has no built-in support for doing this, but C++ users can retrieve stack traces with (for example) the stacktrace library. In JavaScript, exceptions hold a stack
property that contain the stack from the place where it was thrown.
Python[edit]
As an example, the following Python program contains an error.
def a(): i = 0 j = b(i) return j def b(z): k = 5 if z == 0: c() return k + z def c(): error() a()
Running the program under the standard Python interpreter produces the following error message.
Traceback (most recent call last): File "tb.py", line 15, in <module> a() File "tb.py", line 3, in a j = b(i) File "tb.py", line 9, in b c() File "tb.py", line 13, in c error() NameError: name 'error' is not defined
The stack trace shows where the error occurs, namely in the c
function. It also shows that the c
function was called by b
, which was called by a
, which was in turn called by the code on line 15 (the last line) of the program. The activation records for each of these three functions would be arranged in a stack such that the a
function would occupy the bottom of the stack and the c
function would occupy the top of the stack.
Java[edit]
In Java, stack traces can be dumped manually with Thread.dumpStack()
[5] Take the following input:
public class Main { public static void main(String args[]) { demo(); } static void demo() { demo1(); } static void demo1() { demo2(); } static void demo2() { demo3(); } static void demo3() { Thread.dumpStack(); } }
The exception lists functions in descending order, so the most-inner call is first.
java.lang.Exception: Stack trace at java.lang.Thread.dumpStack(Thread.java:1336) at Main.demo3(Main.java:15) at Main.demo2(Main.java:12) at Main.demo1(Main.java:9) at Main.demo(Main.java:6) at Main.main(Main.java:3)
C and C++[edit]
Both C and C++ (pre-C++23) do not have native support for obtaining stack traces, but libraries such as glibc and boost provide this functionality.[6][7] In these languages, some compiler optimizations may interfere with the call stack information that can be recovered at runtime. For instance, inlining can cause missing stack frames, tail call optimizations can replace one stack frame with another, and frame pointer elimination can prevent call stack analysis tools from correctly interpreting the contents of the call stack.[6]
For example, glibc’s backtrace()
function returns an output with the program function and memory address.
./a.out() [0x40067f] ./a.out() [0x4006fe] ./a.out() [0x40070a] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f7e60738f45] ./a.out() [0x400599]
As of C++23, stack traces can be dumped manually by printing the value returned by static member function std::stacktrace::current()
:[8]
std::cout << std::stacktrace::current() << 'n';
Rust[edit]
Rust has two types of errors. Functions that use the panic macro are «unrecoverable» and the current thread will become poisoned experiencing stack unwinding. Functions that return a std::result::Result
are «recoverable» and can be handled gracefully.[9] However, recoverable errors cannot generate a stack trace as they are manually added and not a result of a runtime error.
As of June 2021, Rust has experimental support for stack traces on unrecoverable errors. Rust supports printing to stderr when a thread panics, but it must be enabled by setting the RUST_BACKTRACE
environment variable.[10]
When enabled, such backtraces look similar to below, with the most recent call first.
thread 'main' panicked at 'execute_to_panic', main.rs:3 stack backtrace: 0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace 1: std::panicking::default_hook::{{closure}} 2: std::panicking::default_hook 3: std::panicking::rust_panic_with_hook 4: std::panicking::begin_panic 5: futures::task_impl::with 6: futures::task_impl::park ...
See also[edit]
- Tail call
- Context (computing)
- Stack overflow
- Exception handling
- Call stack
References[edit]
- ^ «libc manual: backtraces». gnu.org. Retrieved 8 July 2014.
- ^ «traceback — Print or retrieve a stack traceback». python.org. Retrieved 8 July 2014.
- ^ «Thread (Java SE 16 & JDK 16)». Java Platform Standard Edition & Java Development Kit Version 16 API Specification. 2021-03-04. Retrieved 2021-07-04.
- ^ «Environment.StackTrace Property (System)». Microsoft Docs. 2021-05-07. Retrieved 2021-07-04.
- ^ «Thread (Java Platform SE 8 )». docs.oracle.com. Retrieved 2021-06-15.
- ^ a b «Backtraces (The GNU C Library)». www.gnu.org. Retrieved 2021-06-15.
- ^ «Getting Started — 1.76.0». www.boost.org. Retrieved 2021-06-15.
- ^ «Working Draft, Standard for Programming Language C++» (PDF). open-std.org. ISO/IEC. 2021-10-23. p. 766.
{{cite web}}
: CS1 maint: url-status (link) - ^ «rustonomicon unwinding — Rust». doc.rust-lang.org.
- ^ «std::backtrace — Rust». doc.rust-lang.org. Retrieved 2021-06-15.
Deploying your Java code to production limits your troubleshooting options. Connecting to your app in production with a debugger is usually out of the question, and you might not even be able to get console access. So even with monitoring, you’re going to end up troubleshooting many problems post-mortem. This means looking at logs and, if you’re lucky, working with a Java stack trace.
That’s right, I said you’re lucky if you have a stack trace. It’s like getting a compass, a map, and a first-class airplane ticket handed to you all at once! Let’s talk about what a Java stack trace is and how you can use it.
What’s a Java Stack Trace?
A stack trace, also called a stack backtrace or even just a backtrace, is a list of stack frames. These frames represent a moment during an application’s execution. A stack frame is information about a method or function that your code called. So the Java stack trace is a list of frames that starts at the current method and extends to when the program started.
Sometimes there’s confusion between a stack and the Stack. A stack is a data structure that acts as a stack of papers on your desk: it’s first-in-last-out. You add documents to the pile and take them off in the reverse order you put them there. The Stack, more accurately called the runtime or call stack, is a set of stack frames a program creates as it executes, organized in a stack data structure.
Let’s look at an example.
Java Stack Trace Example
Let’s take a look at a Java program. This class calls four methods and prints a stack trace to the console from the
public class StackTrace { public static void main(String[] args) { a(); } static void a() { b(); } static void b() { c(); } static void c() { d(); } static void d() { Thread.dumpStack(); } }
When you run the class, you’ll see something like this:
java.lang.Exception: Stack trace at java.base/java.lang.Thread.dumpStack(Thread.java:1383) at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:23) at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:19) at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:15) at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:11) at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:7)
The d() method() is at the top of the stack because that’s where the app generated the trace. The main() method is at the bottom because that’s where the program started. When the program started, the Java runtime executed the main() method. Main() called a(). A() called b(), and b() called c(), which called d(). Finally, d() called dumpStack(), which generated the output. This Java stack trace gives us a picture of what the program did, in the order that it did it.
A Java stack trace is a snapshot of a moment in time. You can see where your application was and how it got there. That’s valuable insight that you can use a few different ways.
How to Use Java Stack Traces
Now that you’ve seen what Java stack traces show you, how can you use them?
Java Exceptions
Stack traces and exceptions are often associated with each other. When you see a Java application throw an exception, you usually see a stack trace logged with it. This is because of how exceptions work.
When Java code throws an exception, the runtime looks up the stack for a method that has a handler that can process it. If it finds one, it passes the exception to it. If it doesn’t, the program exits. So exceptions and the call stack are linked directly. Understanding this relationship will help you figure out why your code threw an exception.
Let’s change our sample code.
First, modify the d() method:
static void d() { throw new NullPointerException("Oops!"); }
Then, change main() and a() so main can catch an exception. You’ll need to add a checked exception to a() so the code will compile.
public static void main(String[] args) { try { a(); } catch (InvalidClassException ice) { System.err.println(ice.getMessage()); } } static void a() throws InvalidClassException { b(); }
You’re deliberately catching the “wrong” exception. Run this code and watch what happens.
Exception in thread "main" java.lang.NullPointerException: Oops! at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:29) at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:24) at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:20) at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:16) at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9)
The exception bubbled up the stack past main() because you were trying to catch a different exception. So the runtime threw it, terminating the application. You can still see a stack trace though, so it’s easy to determine what happened.
Now, change main() to catch a NullPointerException instead. You can remove the checked exception from a() too.
public static void main(String[] args) { try { a(); } catch (NullPointerException ice) { System.err.println(ice.getMessage()); } } static void a() { b(); }
Rerun the program.
Oops!
We lost the stack trace! By only printing the message attached to the exception, you missed some vital context. Unless you can remember why you wrote Oops! in that message, tracking down this problem is going to be
public static void main(String[] args) { try { a(); } catch (NullPointerException npe) { npe.printStackTrace(); } }
Rerun the application.
java.lang.NullPointerException: Oops! at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:28) at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:24) at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:20) at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:16) at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9)
That’s better! We see the stack trace, and it ends at d() where the exception occurred, even though main() printed it.
Logging Java Stack Traces
What if you don’t want to print an error message to the console but to a log file instead? The good news is that most loggers, including Log4j and Logback, will write exceptions with stack traces if you call them with the right arguments.
Pass in the exception object as the last argument to the message, without a formatting directive. So if you used Log4j or Logback with the sample code like this:
logger.error(“Something bad happened:”, npe);
You would see this in your log file:
Something bad happened: java.lang.NullPointerException: Oops! at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:28) at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:24) at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:20) at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:16) at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9)
One of the best things you can do with exceptions and stack traces is to log them so you can use them to isolate a problem. If you get in the habit of printing useful log messages with details like stack traces and log indexing, then search tools, like Scalyr, become one of the most powerful tools in your troubleshooting tool bag.
The Java Debugger
Debuggers work by taking control of a program’s runtime and letting you both observe and control it. To do this, it shows you the program stack and enables you to traverse it in either direction. When you’re in a debugger, you get a more complete picture of a stack frame than you do when looking at stack traces in a log message.
Let’s make a small code change and then throw the sample code into a debugger.
First, add a local variable to the d() method:
static void d() { String message = “Oops.” throw new NullPointerException(message); }
Then add a breakpoint where d() throws the exception in your debugger. I’m using IntelliJ’s debugger for this image.
Here you can see that the string we added to d() is part of the stack frame because it’s a local variable. Debuggers operate inside the Stack and give you a detailed picture of each frame.
Forcing a Thread Dump
Thread dumps are great post-mortem tools, but they can be useful for runtime issues too. If your application stops responding or is consuming more CPU or memory than you expect, you can retrieve information about the running app with jstack.
Modify main() so the application will run until killed:
public static void main(String[] args) throws Exception { try { while(true) { Thread.sleep(1000); } } catch (NullPointerException ice) { ice.printStackTrace(); } }
Run the app, determine its pid, and then run jstack. On Windows, you’ll need to press ctrl-break in the DOS window you’re running your code in.
$ jstack <pid>
Jstack will generate a lot of output.
2019-05-13 10:06:17 Full thread dump OpenJDK 64-Bit Server VM (12+33 mixed mode, sharing): Threads class SMR info: _java_thread_list=0x00007f8bb2727190, length=10, elements={ 0x00007f8bb3807000, 0x00007f8bb2875000, 0x00007f8bb2878000, 0x00007f8bb4000800, 0x00007f8bb300a800, 0x00007f8bb287b800, 0x00007f8bb287f000, 0x00007f8bb28ff800, 0x00007f8bb300b800, 0x00007f8bb3805000 } "main" #1 prio=5 os_prio=31 cpu=60.42ms elapsed=103.32s tid=0x00007f8bb3807000 nid=0x2503 waiting on condition [0x0000700001a0e000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep([email protected]/Native Method) at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9) "Reference Handler" #2 daemon prio=10 os_prio=31 cpu=0.08ms elapsed=103.29s tid=0x00007f8bb2875000 nid=0x4603 waiting on condition [0x0000700002123000] java.lang.Thread.State: RUNNABLE at java.lang.ref.Reference.waitForReferencePendingList([email protected]/Native Method) at java.lang.ref.Reference.processPendingReferences([email protected]/Reference.java:241) at java.lang.ref.Reference$ReferenceHandler.run([email protected]/Reference.java:213) "Finalizer" #3 daemon prio=8 os_prio=31 cpu=0.13ms elapsed=103.29s tid=0x00007f8bb2878000 nid=0x3903 in Object.wait() [0x0000700002226000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait([email protected]/Native Method) - waiting on <0x000000070ff02770> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:155) - locked <0x000000070ff02770> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:176) at java.lang.ref.Finalizer$FinalizerThread.run([email protected]/Finalizer.java:170) "Signal Dispatcher" #4 daemon prio=9 os_prio=31 cpu=0.27ms elapsed=103.28s tid=0x00007f8bb4000800 nid=0x3e03 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #5 daemon prio=9 os_prio=31 cpu=6.12ms elapsed=103.28s tid=0x00007f8bb300a800 nid=0x5603 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE No compile task "C1 CompilerThread0" #7 daemon prio=9 os_prio=31 cpu=12.01ms elapsed=103.28s tid=0x00007f8bb287b800 nid=0xa803 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE No compile task "Sweeper thread" #8 daemon prio=9 os_prio=31 cpu=0.73ms elapsed=103.28s tid=0x00007f8bb287f000 nid=0xa603 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Service Thread" #9 daemon prio=9 os_prio=31 cpu=0.04ms elapsed=103.27s tid=0x00007f8bb28ff800 nid=0xa503 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Common-Cleaner" #10 daemon prio=8 os_prio=31 cpu=0.27ms elapsed=103.27s tid=0x00007f8bb300b800 nid=0xa303 in Object.wait() [0x000070000293b000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait([email protected]/Native Method) - waiting on <0x000000070ff91690> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:155) - locked <0x000000070ff91690> (a java.lang.ref.ReferenceQueue$Lock) at jdk.internal.ref.CleanerImpl.run([email protected]/CleanerImpl.java:148) at java.lang.Thread.run([email protected]/Thread.java:835) at jdk.internal.misc.InnocuousThread.run([email protected]/InnocuousThread.java:134) "Attach Listener" #11 daemon prio=9 os_prio=31 cpu=0.72ms elapsed=0.10s tid=0x00007f8bb3805000 nid=0x5e03 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "VM Thread" os_prio=31 cpu=3.83ms elapsed=103.29s tid=0x00007f8bb2874800 nid=0x3703 runnable "GC Thread#0" os_prio=31 cpu=0.13ms elapsed=103.31s tid=0x00007f8bb282b800 nid=0x3003 runnable "G1 Main Marker" os_prio=31 cpu=0.26ms elapsed=103.31s tid=0x00007f8bb2845000 nid=0x3103 runnable "G1 Conc#0" os_prio=31 cpu=0.04ms elapsed=103.31s tid=0x00007f8bb3810000 nid=0x3303 runnable "G1 Refine#0" os_prio=31 cpu=0.39ms elapsed=103.31s tid=0x00007f8bb2871000 nid=0x3403 runnable "G1 Young RemSet Sampling" os_prio=31 cpu=13.60ms elapsed=103.31s tid=0x00007f8bb2872000 nid=0x4d03 runnable "VM Periodic Task Thread" os_prio=31 cpu=66.44ms elapsed=103.27s tid=0x00007f8bb2900800 nid=0xa403 waiting on condition JNI global refs: 5, weak refs: 0
My application was running 11 threads, and jstack generated a stack trace for all of them. The first thread, helpfully named main, is the one we’re concerned with. You can see it sleeping on wait().
Java Stack Traces: Your Roadmap
A stack trace is more than just a picture inside your application. It’s a snapshot of a moment in time that includes every step your code took to get there. There’s no reason to dread seeing one in your logs because they’re a gift from Java that tells you exactly what happened. Make sure you’re logging them when an error crops up and send them to a tool like Scalyr so they’re easy to find.
Now that you understand what a Java stack trace is and how to use it, take a look at your code. Are you throwing away critical information about errors and exceptions in your code? Is there a spot where a call to Thread.dumpstack() might help you isolate a recurring bug? Perhaps it’s time to run your app through the debugger a few times with some strategically-chosen breakpoints.
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 22
37 } 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 more
Caused 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 more
Caused 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).
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 22
37 } 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 more
Caused 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 more
Caused 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).
- NullPointerException if
obj
isnull
in code which callsobj.someMethod()
- ArithmeticException if a division by zero happens in integer arithmetic, ie
1/0
— curiously there is no Exception if this is a floating-point calculation though,1.0/0.0
returnsinfinity
just fine! - NullPointerException if a
null
Integer is unboxed to anint
in code like this:Integer a=null; a++;
- There are some other examples in the Java Language Specification, so it’s important to be aware that Exceptions can arise without being explicitly thrown.
- Our users are shielded from having to care about the
ArithmeticException
— giving us flexibility to change how commons-lang is used. - More context can be added, eg stating that it’s the number of FooBars that is causing the problem.
- It can make stack traces easier to read, too, as we’ll see below.
- Your code calls methods in a Library
- Your code is called by methods in a Framework
- Handle Twilio Debugger Events with Python and Flask
- Locally Developing and Debugging Twilio Applications
- How to Debug and Fix PHP Mail in Local Host
When things go wrong in a running Java application, often the first sign you will have is lines printed to the screen that look like the code below. This is a Java Stack Trace, and in this post, I’ll explain what they are, how they are made, and how to read and understand them. If that looks painful to you then read on…
Exception in thread "main" java.lang.RuntimeException: Something has gone wrong, aborting!
at com.myproject.module.MyProject.badMethod(MyProject.java:22)
at com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)
at com.myproject.module.MyProject.anotherMethod(MyProject.java:14)
at com.myproject.module.MyProject.someMethod(MyProject.java:10)
at com.myproject.module.MyProject.main(MyProject.java:6)
Java Stack Trace: What It Is and How It Works
A stack trace (also known as a stack backtrace or stack traceback) is an incredibly useful tool when it comes to debugging code. Learn more about what it is and how it works.
What Is a Java Stack Trace?
A stack trace shows the call stack (sets of active stack frames) and provides information on the methods that your code called. Usually, a stack trace is shown when an Exception is not handled correctly in code. (An exception is what a runtime environment uses to tell you that there’s an error in your code.) This may be one of the built-in Exception types, or a custom Exception created by a program or a library.
The stack trace contains the Exception’s type and a message, and a list of all the method calls which were in progress when it was thrown.
How to Read a Java Stack Trace
Let’s dissect that stack trace. The first line tells us the details of the Exception:
This is a good start. Line 2 shows what code was running when that happened:
That helps us narrow down the problem, but what part of the code called badMethod
? The answer is on the next line down, which can be read in the exact same way. And how did we get there? Look on the next line. And so on, until you get to the last line, which is the main
method of the application. Reading the stack trace from bottom to top you can trace the exact path from the beginning of your code, right to the Exception.
What Triggered the Stack Trace?
The thing that causes an Exception is usually an explicit throw
statement. Using the file name and line number you can check exactly what code threw the Exception. It will probably look something like this:
throw new RuntimeException("Something has gone wrong, aborting!");
This is a great place to start looking for the underlying problem: are there any if statements around that? What does that code do? Where does the data used in that method come from?
It is also possible for code to throw an Exception without an explicit throw
statement, for example you can get:
Dealing with Exceptions Thrown by Libraries
One of the great strengths of Java is the huge number of libraries available. Any popular library will be well tested so generally when faced with an Exception from a library, it’s best to check first whether the error is caused by how our code uses it.
For example, if we’re using the Fraction class from Apache Commons Lang and pass it some input like this:
Fraction.getFraction(numberOfFoos, numberOfBars);
If numberOfBars
is zero, then the stack trace will be like this:
Exception in thread "main" java.lang.ArithmeticException: The denominator must not be zero
at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143)
at com.project.module.MyProject.anotherMethod(MyProject.java:17)
at com.project.module.MyProject.someMethod(MyProject.java:13)
at com.project.module.MyProject.main(MyProject.java:9)
Many good libraries provide Javadoc which includes information about what kinds of Exceptions may be thrown and why. In this case Fraction.getFraction
has documented will throw an ArithmeticException if a Fraction has a zero denominator. Here it’s also clear from the message, but in more complicated or ambiguous situations the docs can be a great help.
To read this stack trace, start at the top with the Exception’s type — ArithmeticException
and message The denominator must not be zero
. This gives an idea of what went wrong, but to discover what code caused the Exception, skip down the stack trace looking for something in the package com.myproject
(it’s on the 3rd line here), then scan to the end of the line to see where the code is (MyProject.java:17
). That line will contain some code that calls Fraction.getFraction
. This is the starting point for investigation: What is passed to getFraction
? Where did it come from?
In big projects with many libraries, stack traces can be hundreds of lines long so if 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
Let’s say we are working on a big project that deals with fictional FooBars
, and our code is going to be used by others. We might decide to catch the ArithmeticException from Fraction
and re-throw it as something project-specific, which looks like this:
try {
....
Fraction.getFraction(x,y);
....
} catch ( ArithmeticException e ){
throw new MyProjectFooBarException("The number of FooBars cannot be zero", e);
}
Catching the ArithmeticException
and rethrowing it has a few benefits:
It isn’t necessary to catch-and-rethrow on every Exception, but where there seems to be a jump in the layers of your code, like calling into a library, it often makes sense.
Notice that the constructor for MyProjectFooBarException
takes 2 arguments: a message and the Exception which caused it. Every Exception in Java has a cause
field, and when doing a catch-and-rethrow like this then you should always set that to help people debug errors. A stack trace might now look something like this:
Exception in thread "main" com.myproject.module.MyProjectFooBarException: The number of FooBars cannot be zero
at com.myproject.module.MyProject.anotherMethod(MyProject.java:19)
at com.myproject.module.MyProject.someMethod(MyProject.java:12)
at com.myproject.module.MyProject.main(MyProject.java:8)
Caused by: java.lang.ArithmeticException: The denominator must not be zero
at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143)
at com.myproject.module.MyProject.anotherMethod(MyProject.java:17)
... 2 more
The most recently thrown Exception is on the first line, and the location where it was thrown is still on line 2. However, this type of stack trace can cause confusion because the catch-and-rethrow has changed the order of method calls compared to the stack traces we saw before. The main method is no longer at the bottom, and the code which first threw an Exception is no longer at the top. When you have multiple stages of catch-and-rethrow then it gets bigger but the pattern is the same:
Check the sections from first to last looking for your code, then read relevant sections from bottom to top.
Libraries vs Frameworks
The difference between a Library and a Framework in Java is:
A common type of Framework is a web application server, like SparkJava or Spring Boot. Using SparkJava and Commons-Lang with our code we might see a stack trace like this:
com.framework.FrameworkException: Error in web request
at com.framework.ApplicationStarter.lambda$start$0(ApplicationStarter.java:15)
at spark.RouteImpl$1.handle(RouteImpl.java:72)
at spark.http.matching.Routes.execute(Routes.java:61)
at spark.http.matching.MatcherFilter.doFilter(MatcherFilter.java:134)
at spark.embeddedserver.jetty.JettyHandler.doHandle(JettyHandler.java:50)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1568)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:503)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: com.project.module.MyProjectFooBarException: The number of FooBars cannot be zero
at com.project.module.MyProject.anotherMethod(MyProject.java:20)
at com.project.module.MyProject.someMethod(MyProject.java:12)
at com.framework.ApplicationStarter.lambda$start$0(ApplicationStarter.java:13)
... 16 more
Caused by: java.lang.ArithmeticException: The denominator must not be zero
at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143)
at com.project.module.MyProject.anotherMethod(MyProject.java:18)
... 18 more
OK that is getting quite long now. As before we should suspect our own code first, but it’s getting harder and harder to find where that is. At the top there is the Framework’s Exception, at the bottom the Library’s and right in the middle is our own code. Phew!
A complex Framework and Library can create a dozen or more Caused by:
sections, so a good strategy is to jump down those looking for your own code: Caused by: com.myproject...
Then read that section in detail to isolate the problem.
Java Stack Trace Takeaways
Learning how to understand stack traces and read them quickly will let you home in on problems and makes debugging much less painful. It’s a skill that improves with practice, so next time you see a big stack trace don’t be intimidated — there is a lot of useful information there if you know how to extract it.
If you have any tips or tricks about dealing with Java stack traces, I’d love to hear about them so get in touch with me and let’s share what we know.
mgilliard@twilio.com
For more resources on debugging code and applications, check out these articles: