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.
As a developer, stack traces are one of the most common error types you’ll run into. Every developer makes mistakes, including you. When you make a mistake, your code will likely exit and print a weird-looking message called a stack trace.
But actually, a stack trace represents a path to a treasure, like a pirate map. It shows you the exact route your code traversed leading up to the point where your program printed an exception.
But, how do you read a stack trace? How does a stack trace help with troubleshooting your code? Let’s start with a comprehensive definition of a stack trace.
What Is a Stack Trace?
To put it simply, a stack trace represents a call stack at a certain point in time. To better understand what a call stack is, let’s dive a bit deeper into how programming languages work.
A stack is actually a data type that contains a collection of elements. The collection works as a last-in, first-out (LIFO) collection. Each element in this collection represents a function call in your code that contains logic.
Whenever a certain function call throws an error, you’ll have a collection of function calls that lead up to the call that caused the particular problem. This is due to the LIFO behavior of the collection that keeps track of underlying, previous function calls.
This also implies that a stack trace is printed top-down. The stack trace first prints the function call that caused the error and then prints the previous underlying calls that led up to the faulty call. Therefore, reading the first line of the stack trace shows you the exact function call that threw an error.
Now that you have a deeper understanding of how a stack trace works, let’s learn to read one.
Stack traces are constructed in a very similar way in most languages: they follow the LIFO stack approach. Let’s take a look at the Java stack trace below.
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)
The first line tells us the exact error that caused the program to print a stack trace. We see a NullPointerException, which is a common mistake for Java programmers. From the Java documentation, we note that a NullPointerException is thrown when an application attempts to use “null” in a case where an object is required.
Now we know the exact error that caused the program to exit. Next, let’s read the previous call stack. You see three lines that start with the word “at”. All three of those lines are part of the call stack.
The first line represents the function call where the error occurred. As we can see, the getTitle function of the Book class is the last executed call. Furthermore, the error occurred at line 16 in the Book class file.
The other calls represent previous calls that lead up to the getTitle function call. In other words, the code produced the following execution path:
- Start in main() function.
- Call getBookTitles() function in Author class at line 25.
- Call getTitle() function in Book class at line 16.
In some cases, you’ll experience chained exceptions in your stack trace. As you could have guessed, the stack trace shows you multiple exceptions that have occurred. Each individual exception is marked by the words “Caused by”.
The lowest “Caused by” statement is often the root cause, so that’s where you should look at first to understand the problem. Here’s an example stack trace that includes several “Caused by” clauses. As you can see, the root issue in this example is the database call dbCall.
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)
Caused by: com.example.ServiceException: Service level error
at com.example.myproject.Service.serviceMethod(Service.java:15)
... 1 more
Caused by: com.example.DatabaseException: Database level error
at com.example.Database.dbCall(Database.java:39)
at com.example.myproject.Service.serviceMethod(Service.java:13)
... 2 more
Hopefully, this information gives you a better understanding of how to approach a call stack. So, what’s a stack trace used for?
How to Use a Stack Trace
A stack trace is a valuable piece of information that can be used for debugging purposes. Whenever you encounter an error in your application, you want to be able to quickly debug the problem. Initially, developers should look in the application logs for the stack trace, because often, the stack trace tells you the exact line or function call that caused a problem.
It’s a very valuable piece of information for quickly resolving bugs because it points to the exact location where things went wrong.
In addition to telling you the exact line or function that caused a problem, a stack trace also tracks important metrics that monitor the health of your application. For example, if the average number of stack traces found in your logs increases, you know a bug has likely occurred. Furthermore, a low level of stack trace exceptions indicates that your application is in good health.
In short, stack traces and the type of errors they log can reveal various metrics related to your application as explained in the example.
Common Problems With Stack Traces and Third-Party Packages
Often, your projects will use many third-party code packages or libraries. This is a common approach for languages such as PHP or JavaScript. There is a rich ecosystem of packages maintained by the Open Source community.
In some cases, an error occurs when you send incorrect input to one of the third-party libraries you use. As you might expect, your program will print a stack trace of the function calls leading up to the problem.
However, now that you’re dealing with a third-party library, you’ll have to read many function calls before you recognize one associated with your code. In some cases, like when too many function calls happen within a third-party package, you may not even see any references to your code.
You can still try to debug your code by looking at where a particular package was used. However, if you use this particular package frequently throughout your code, debugging your application won’t be easy.
But there’s a solution to the third-party stack problem. Let’s check it out!
Solving the Third-Party Stack Trace Problem
Luckily, you can solve third-party stack trace problems by catching exceptions. A call to a third-party library may cause an exception, which will cause your program to print a stack trace containing function calls coming from within the third-party library. However, you can catch the exception in your code with the use of a try-catch statement, which is native to many programming languages such as Java or Node.js.
If you use a try-catch statement, the stack trace will start from the point where you included the try-catch logic. This presents you with a more actionable and readable stack trace that doesn’t traverse into third-party library’s function calls. It’s an effective and simple solution to make your stack traces easy to understand.
On top of that, when your program throws a Throwable instance, you can call the getStackTrace() function on the instance to access the stack trace. You can then decide to print the stack trace to your terminal so you can collect the information for log analysis. Here’s a small example where we throw an exception and manually print the stack trace.
import java.io.*;
class Sum {
// Main Method
public static void main(String[] args) throws Exception {
try {
// add positive numbers
addPositiveNumbers(5, -5);
} catch (Throwable e) {
StackTraceElement[] stktrace = e.getStackTrace();
// print element of stktrace
for (int i = 0; i < stktrace.length; i++) {
System.out.println("Index " + i
+ " of stack trace contains = "
+ stktrace[i].toString());
}
}
}
// method which adds two positive number
public static void addPositiveNumbers(int a, int b) throws Exception {
if (a < 0 || b < 0) {
throw new Exception("Numbers are not Positive");
} else {
System.out.println(a + b);
}
}
}
The printed output will look like this.
Index 0 of stack trace contains = Sum.addPositiveNumbers(File.java:26) Index 1 of stack trace contains = Sum.main(File.java:6)
Next, let’s learn how log management and stack traces work together.
Log Management and Stack Traces
You might wonder what log management and stack traces have to do with each other, and actually, they’re very compatible.
It’s best practice for your DevOps team to implement a logging solution. Without an active logging solution, it’s much harder to read and search for stack traces. A logging solution provides you with an easy-to-use interface and better filtering capabilities.
A log management solution helps aggregate logs, index them, and make them searchable, all from a single interface. You can run advanced queries to find specific logs or stack trace information. This approach is much faster than using the CTRL+F key combination to look through your logs.
Conclusion
To summarize, we focused on the need for a logging solution to access stack traces and any other relevant information your application outputs. A stack trace is one of the most valuable pieces of information to help developers identify problems quickly.
Furthermore, a stack trace shows an exact execution path, providing context to developers trying to solve bugs. The first line in the call stack represents the last executed function call, so remember to always read a stack trace top-down. That first function call is responsible for throwing an exception.
Want to decrease your bug resolution time? Check out Scalyr’s log management solution. If you want to learn more about log formatting and best practices for logging, check out this log formatting article.
A Java stack trace is displayed when an error or exception occurs. The stack trace, also called a backtrace, consists of a collection of stack records, which store an application’s movement during its execution.
The stack trace includes information about program subroutines and can be used to debug or troubleshoot and is often used to create log files. These exceptions could be custom (defined by the user) or built-in. Examples include RuntimeException,NullPointerException, andArrayIndexOutofBoundsException.
Now that you know what a stack trace is, let’s take a look at some examples, how to analyze stack traces, and how you can avoid a stack trace altogether with error handling.
Examples of Java Stack Traces
Example 1 — Temperature Conversion from Celsius to Fahrenheit
Let’s look at an example of converting temperatures from Celsius to Fahrenheit. Only an integer or float input is valid here. But if we try to provide another data type, such as a string, the compiler will throw an exception and print the stack trace.
import java.util.Scanner;
public class hello{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter value in Celsius to convert in fahrenheit:");
double Celsius = scanner.nextFloat();
double fahrenheit = (Celsius * 1.8)+32;
System.out.printf("%.1f degrees Celsuis is %.1f degrees in Fahrenheit ",Celsius,fahrenheit);
}
}
When we run the above code and enter some invalid value, let’s say the string «hero,» we get the following output:
Enter value in Celsius to convert in fahrenheit: hero
Exception in thread "main" java.util.InputMismatchException
at java.base/java.util.Scanner.throwFor(Scanner.java:939)
at java.base/java.util.Scanner.next(Scanner.java:1594)
at java.base/java.util.Scanner.nextFloat(Scanner.java:2496)
at com.example.myJavaProject.hello.main(hello.java:12)
Example 2 — Function Chaining
This is an example of function chaining, in which one function calls another in a chain-like fashion. Unlike in Example 1, no exception is thrown here, but the stack trace is explicitly printed using the dumpstack() method (a useful method when creating log files). This is good practice because we can use this code later for maintenance or to check the overall health or condition of the application.
public class Example {
public static void main(String args[]) {
f1();
}
static void f1() {
f2();
}
static void f2() {
f3();
}
static void f3() {
f4();
}
static void f4() {
Thread.dumpStack();
}
}
When the above code is executed, we get the following output:
java.lang.Exception: Stack trace
at java.base/java.lang.Thread.dumpStack(Thread.java:1380)
at com.example.myJavaProject.Example.f4(Example.java:25)
at com.example.myJavaProject.Example.f3(Example.java:20)
at com.example.myJavaProject.Example.f2(Example.java:15)
at com.example.myJavaProject.Example.f1(Example.java:10)
at com.example.myJavaProject.Example.main(Example.java:6)
How to Read and Analyze Example 1’s Stack Trace
Let’s consider Example 1 for this analysis. Below is the breakdown of the output from its execution:
The first line in the stack trace:

The bottom line in the stack trace:

Now, let’s look at the entire stack trace and try to analyze it:
Enter value in Celsius to convert in fahrenheit: hero
Exception in thread "main" java.util.InputMismatchException
at java.base/java.util.Scanner.throwFor(Scanner.java:939)
at java.base/java.util.Scanner.next(Scanner.java:1594)
at java.base/java.util.Scanner.nextFloat(Scanner.java:2496)
at com.example.myJavaProject.hello.main(hello.java:12)
The main() method is at the bottom of the stack because that is where the program began. By reading from bottom to top, we can now identify where and what exception is being raised. Tracing the source of this error back to the main() method reveals that an exception occurs when the user’s input is taken.
The second line from the top shows that the float input was taken using the function nextFloat(), which in turn calls the next() function, which in turn calls the throwFor() function. As a result, it throws an InputMismatchException.
How to Fix Example 1’s Code Using Error Handling and Stack Traces
Stack traces and exceptions are clearly related, as evidenced by the preceding examples. Stack traces can be avoided; in short, some common error handling techniques can be used to handle and resolve any exceptions thrown by the code during execution. The technique listed below can help avoid a stack trace.
Examine, Investigate, and Handle Java Errors
It’s common for amateur programmers to overlook exceptions in their code. Being able to examine, investigate, and handle mistakes can be very helpful prior to moving to the next step. Let’s handle the exception in Example 1 by using try and catch statements.
import java.util.Scanner;
public class hello {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter value in Celsius to convert in fahrenheit:");
try {
double Celsius = scanner.nextFloat();
double fahrenheit = (Celsius * 1.8) + 32;
System.out.printf("%.1f degrees Celsuis is %.1f degrees in Fahrenheit ", Celsius, fahrenheit);
} catch (InputMismatchException e) {
System.out.println("Wrong input type entered...exiting the program");
}
}
}
In the above code, we have used a try—catch block to catch the exception and then print a custom message to notify the user to enter a valid input.
When the code above is executed, we get the following output:
Enter value in Celsius to convert in fahrenheit: hero
Wrong input type entered…exiting the program
Process finished with exit code 0
With the help of the try and catch blocks, the code to be tested is placed in the try block and any exception thrown by the code is handled in the catch block.
This is the most commonly used method to handle exceptions in Java and thus avoid stack traces.
Track, Analyze and Manage Errors With Rollbar
Managing errors and exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyse, and manage errors in real-time can help you proceed with more confidence. Rollbar automates error monitoring and triaging, making fixing Java errors easier than ever. Sign Up Today!
Что такое stack trace, и как с его помощью находить ошибки при разработке приложений?
Иногда при запуске своего приложения я получаю подобную ошибку:
Мне сказали, что это называется «трассировкой стека» или «stack trace». Что такое трассировка? Какую полезную информацию об ошибке в разрабатываемой программе она содержит?
Немного по существу: довольно часто я вижу вопросы, в которых начинающие разработчики, получая ошибку, просто берут трассировки стека и какой-либо случайный фрагмент кода без понимания, что собой представляет трассировка и как с ней работать. Данный вопрос предназначен специально для начинающих разработчиков, которым может понадобиться помощь в понимании ценности трассировки стека вызовов.
![]()
Простыми словами, трассировка стека – это список методов, которые были вызваны до момента, когда в приложении произошло исключение.
Простой случай
В указанном примере мы можем точно определить, когда именно произошло исключение. Рассмотрим трассировку стека:
Это пример очень простой трассировки. Если пойти по списку строк вида «at…» с самого начала, мы можем понять, где произошла ошибка. Мы смотрим на верхний вызов функции. В нашем случае, это:
Для отладки этого фрагмента открываем Book.java и смотрим, что находится на строке 16 :
Это означает то, что в приведенном фрагменте кода какая-то переменная (вероятно, title ) имеет значение null .
Пример цепочки исключений
Иногда приложения перехватывают исключение и выбрасывают его в виде другого исключения. Обычно это выглядит так:
Трассировка в этом случае может иметь следующий вид:
В этом случае разница состоит в атрибуте «Caused by» («Чем вызвано»). Иногда исключения могут иметь несколько секций «Caused by». Обычно необходимо найти исходную причину, которой оказывается в самой последней (нижней) секции «Caused by» трассировки. В нашем случае, это:
Аналогично, при подобном исключении необходимо обратиться к строке 22 книги Book.java , чтобы узнать, что вызвало данное исключение – NullPointerException .
Еще один пугающий пример с библиотечным кодом
Как правило, трассировка имеет гораздо более сложный вид, чем в рассмотренных выше случаях. Приведу пример (длинная трассировка, демонстрирующая несколько уровней цепочек исключений):
В этом примере приведен далеко не полный стек вызовов. Что вызывает здесь наибольший интерес, так это поиск функций из нашего кода – из пакета com.example.myproject . В предыдущем примере мы сначала хотели отыскать «первопричину», а именно:
Однако все вызовы методов в данном случае относятся к библиотечному коду. Поэтому мы перейдем к предыдущей секции «Caused by» и найдем первый вызов метода из нашего кода, а именно:
Что такое трассировка стека и как ее использовать для отладки ошибок приложения?
Иногда, когда я запускаю свое приложение, я получаю ошибку, которая выглядит примерно так:
Люди называют это «трассировкой стека». Что такое трассировка стека? Что он может сказать мне об ошибке в моей программе?
По поводу этого вопроса — довольно часто я вижу, как начинающий программист «получает ошибку» и просто вставляет свою трассировку стека и какой-то случайный блок кода, не понимая, что такое трассировка стека и как они могут ее использовать. Этот вопрос предназначен в качестве справочника для начинающих программистов, которым может потребоваться помощь в понимании значения трассировки стека.
- 28 Кроме того, если строка трассировки стека не содержит имени файла и номера строки, класс для этой строки не был скомпилирован с отладочной информацией.
Проще говоря, трассировки стека — это список вызовов методов, в процессе которых приложение находилось в момент создания исключения.
Простой пример
С помощью примера, приведенного в вопросе, мы можем точно определить, где в приложении возникло исключение. Давайте посмотрим на трассировку стека:
Это очень простая трассировка стека. Если мы начнем с начала списка «в . », мы сможем сказать, где произошла наша ошибка. Мы ищем самый верхний вызов метода, который является частью нашего приложения. В данном случае это:
Чтобы отладить это, мы можем открыть Book.java и посмотрите на строку 16 , который:
Это означало бы, что что-то (возможно, title ) является null в приведенном выше коде.
Пример с цепочкой исключений
Иногда приложения перехватывают исключение и повторно генерируют его как причину другого исключения. Обычно это выглядит так:
Это может дать вам трассировку стека, которая выглядит так:
Что отличает этот, так это «Причина». Иногда исключения содержат несколько разделов «Причина». Для них обычно требуется найти «основную причину», которая будет одним из самых низких разделов «Причина» в трассировке стека. В нашем случае это:
Опять же, за этим исключением мы хотели бы посмотреть на строку 22 из Book.java чтобы увидеть, что может вызвать NullPointerException Вот.
Более устрашающий пример с библиотечным кодом
Обычно трассировки стека намного сложнее, чем два приведенных выше примера. Вот пример (он длинный, но демонстрирует несколько уровней связанных исключений):
В этом примере многое другое. Что нас больше всего беспокоит, так это поиск методов из наш код, что было бы чем угодно в com.example.myproject пакет. Во втором примере (выше) мы сначала хотели бы найти основную причину, а именно:
Однако все вызовы методов под этим кодом являются библиотечным кодом. Итак, мы перейдем к пункту «Причина» над ним и найдем первый вызов метода, исходящий из нашего кода, а именно:
Как и в предыдущих примерах, мы должны посмотреть на MyEntityService.java онлайн 59 , потому что именно здесь возникла эта ошибка (это немного очевидно, что пошло не так, поскольку SQLException сообщает об ошибке, но процедура отладки — это то, что нам нужно).
- 4 @RobHruska — Очень хорошо объяснено. +1. Знаете ли вы какие-либо парсеры, которые принимают трассировку исключения в виде строки и предоставляют полезные методы для анализа трассировки стека? — например, getLastCausedBy () или getCausedByForMyAppCode («com.example.myproject»)
- 1 @AndyDufresne — я не встречал ни одного, но, опять же, я тоже особо не смотрел.
- 1 Предлагаемое улучшение: объясните первую строку трассировки стека, которая начинается с Exception in thread ‘main’ в вашем первом примере. Я думаю, было бы особенно полезно объяснить, что эта строка часто сопровождается сообщением, например значением переменной, которое может помочь диагностировать проблему. Я сам попытался внести правку, но мне не удается уместить эти идеи в существующую структуру вашего ответа.
- 5 Также в java 1.7 добавлено «Подавлено:», в котором перечислены трассировки стека подавленных исключений перед отображением «Вызвано:» для этого исключения. Он автоматически используется конструкцией try-with-resource: docs.oracle.com/javase/specs/jls/se8/html/… и содержит исключения, если таковые возникли при закрытии ресурса (ов).
- Существует JEP openjdk.java.net/jeps/8220715, цель которого — еще больше улучшить понятность, особенно NPE, путем предоставления таких деталей, как «Невозможно записать поле ‘nullInstanceField’, потому что ‘this.nullInstanceField’ имеет значение null».
Я отправляю этот ответ, поэтому самый верхний ответ (при сортировке по активности) не является просто неправильным.
Что такое Stacktrace?
Трассировка стека — очень полезный инструмент отладки. Он показывает стек вызовов (то есть стек функций, которые были вызваны до этого момента) в момент возникновения неперехваченного исключения (или время, когда трассировка стека была сгенерирована вручную). Это очень полезно, потому что это не только показывает вам, где произошла ошибка, но и то, как программа оказалась в этом месте кода. Это приводит к следующему вопросу:
Что такое исключение?
Исключение — это то, что среда выполнения использует, чтобы сообщить вам, что произошла ошибка. Популярные примеры: NullPointerException, IndexOutOfBoundsException или ArithmeticException. Каждая из них возникает, когда вы пытаетесь сделать что-то, что невозможно. Например, NullPointerException будет выброшено, когда вы попытаетесь разыменовать Null-объект:
Что мне делать с трассировками стека / исключениями?
Сначала выясните, что вызывает исключение. Попробуйте поискать в Google название исключения, чтобы выяснить, в чем причина этого исключения. В большинстве случаев это вызвано неправильным кодом. В приведенных выше примерах все исключения вызваны неправильным кодом. Итак, для примера NullPointerException вы можете убедиться, что a в то время никогда не бывает нулевым. Вы можете, например, инициализировать a или включите проверку, подобную этой:
Таким образом, нарушающая строка не выполняется, если a==null . То же самое и с другими примерами.
Иногда вы не можете быть уверены, что не получите исключения. Например, если вы используете сетевое соединение в своей программе, вы не можете помешать компьютеру потерять подключение к Интернету (например, вы не можете запретить пользователю отключать сетевое подключение компьютера). В этом случае сетевая библиотека, вероятно, выдаст исключение. Теперь вы должны поймать исключение и справиться Это. Это означает, что в примере с сетевым подключением вы должны попытаться повторно открыть соединение или уведомить пользователя или что-то в этом роде. Кроме того, всякий раз, когда вы используете catch, всегда перехватывайте только исключение, которое хотите перехватить, не используйте общие операторы catch, такие как catch (Exception e) это поймает все исключения. Это очень важно, потому что в противном случае вы можете случайно поймать неправильное исключение и отреагировать неправильно.
Почему я не должен использовать catch (Exception e) ?
Давайте воспользуемся небольшим примером, чтобы показать, почему не следует просто перехватывать все исключения:
Этот код пытается поймать ArithmeticException вызвано возможным делением на 0. Но он также улавливает возможное NullPointerException это брошено, если a или же b находятся null . Это означает, что вы можете получить NullPointerException но вы будете рассматривать это как ArithmeticException и, вероятно, сделаете неправильный поступок. В лучшем случае вы все равно пропустите исключение NullPointerException. Подобные вещи значительно усложняют отладку, так что не делайте этого.
TL; DR
- Выясните, в чем причина исключения, и устраните ее, чтобы исключение вообще не генерировалось.
Если 1. невозможно, перехватите конкретное исключение и обработайте его.
- Никогда не добавляйте просто try / catch и игнорируйте исключение! Не делай этого!
- Никогда не использовать catch (Exception e) , всегда перехватывайте определенные исключения. Это избавит вас от головной боли.
- 1 хорошее объяснение того, почему нам следует избегать маскировки ошибок
- 2 Я отправляю этот ответ, поэтому самый верхний ответ (при сортировке по активности) не является просто неправильным Я понятия не имею, о чем вы говорите, поскольку это, вероятно, уже изменилось. Но принятый ответ определенно интереснее;)
- 1 Насколько я знаю, тот, который я имел в виду, к настоящему времени удален. По сути, он гласил: «просто попробуйте catch (Exception e) и игнорируйте все ошибки». Принятый ответ намного старше моего, поэтому я стремился высказать немного другое мнение по этому поводу. Я не думаю, что кому-то поможет просто скопировать чей-то ответ или осветить то, что другие люди уже хорошо осветили.
- Сказать «Не ловить исключение» — это заблуждение — это только один вариант использования. Ваш пример великолепен, но как насчет того, где вы находитесь в верхней части цикла потока (внутренний запуск)? Вы должны ВСЕГДА перехватывать исключение (или, может быть, Throwable) там и регистрировать его, чтобы оно не исчезло незаметно (исключения, генерируемые при запуске, обычно не регистрируются правильно, если вы не настроили свой поток / регистратор для этого).
- 1 Я не включил этот особый случай, поскольку он имеет значение только для многопоточности. В однопоточном режиме просочившееся исключение убивает программу и явно регистрируется в журнале. Если кто-то не знает, как правильно обрабатывать исключения, он обычно еще не знает, как использовать многопоточность.
Чтобы добавить к тому, что сказал Роб. Установка точек останова в приложении позволяет выполнять пошаговую обработку стека. Это позволяет разработчику использовать отладчик, чтобы увидеть, в какой именно момент метод делает что-то непредвиденное.
Поскольку Роб использовал NullPointerException (NPE), чтобы проиллюстрировать что-то общее, мы можем помочь устранить эту проблему следующим образом:
если у нас есть метод, который принимает такие параметры, как: void (String firstName)
В нашем коде мы хотели бы оценить это firstName содержит значение, мы бы сделали это так: if(firstName == null || firstName.equals(»)) return;
Вышесказанное мешает нам использовать firstName как небезопасный параметр. Поэтому, выполняя нулевые проверки перед обработкой, мы можем помочь убедиться, что наш код будет работать правильно. Чтобы расширить пример, в котором используется объект с методами, мы можем посмотреть здесь:
if(dog == null || dog.firstName == null) return;
Выше приведен правильный порядок проверки на нули, мы начинаем с базового объекта, в данном случае dog, а затем начинаем спускаться по дереву возможностей, чтобы убедиться, что все правильно перед обработкой. Если бы порядок был изменен, NPE потенциально мог бы быть брошен, и наша программа вылетела бы.
- Согласовано. Этот подход можно использовать, чтобы узнать, какая ссылка в заявлении null когда NullPointerException рассматривается, например.
- 16 При работе со String, если вы хотите использовать метод equals, я думаю, что лучше использовать константу в левой части сравнения, например: Вместо: if (firstName == null || firstName.equals (» «)) возвращение; Я всегда использую: if ((«»). Equals (firstName)) Это предотвращает исключение Nullpointer
Есть еще одна функция stacktrace, предлагаемая семейством Throwable — возможность манипулировать информация трассировки стека.
Стандартное поведение:
Управляемая трассировка стека:
- 2 Не знаю, как я к этому отношусь . учитывая характер потока, я бы посоветовал новым разработчикам не определять собственную трассировку стека.
Чтобы понять имя: Трассировка стека — это список исключений (или вы можете сказать список «Причина по»), от самого поверхностного исключения (например, исключения уровня обслуживания) до самого глубокого (например, исключения базы данных). Точно так же, как причина, по которой мы называем это «стеком», заключается в том, что стек первым пришел последним (FILO), самое глубокое исключение произошло в самом начале, затем была сгенерирована цепочка исключений, серия последствий, поверхностное исключение было последним. одно произошло вовремя, но мы видим это в первую очередь.
Ключ 1: Здесь необходимо понять сложную и важную вещь: самая глубокая причина может не быть «основной причиной», потому что, если вы напишете какой-то «плохой код», он может вызвать какое-то исключение внизу, которое глубже, чем его уровень. Например, неправильный sql-запрос может вызвать сброс соединения SQLServerException в нижней части вместо синтаксической ошибки, которая может быть только в середине стека.
-> Найдите основную причину, посередине — это ваша работа.

Ключ 2: Еще одна сложная, но важная вещь — внутри каждого блока «Причина по», первая строка была самым глубоким слоем и занимала первое место для этого блока. Например,
Book.java:16 был вызван Auther.java:25, который был вызван Bootstrap.java:14, Book.java:16 был основной причиной. Здесь прикрепите диаграмму, отсортируйте стек трассировки в хронологическом порядке.

Чтобы добавить к другим примерам, есть внутренние (вложенные) классы которые появляются с $ подписать. Например:
Результатом будет эта трассировка стека:
В других сообщениях описывается, что такое трассировка стека, но с ней все еще может быть сложно работать.
Если вы получили трассировку стека и хотите отследить причину исключения, хорошей отправной точкой для понимания этого будет использование Консоль Java Stack Trace в Затмение. Если вы используете другую IDE, может быть аналогичная функция, но этот ответ касается Eclipse.
Во-первых, убедитесь, что все ваши источники Java доступны в проекте Eclipse.
Тогда в Ява перспективы, нажмите на Приставка вкладка (обычно внизу). Если представление консоли не отображается, перейдите к пункту меню Окно -> Показать вид и выберите Приставка.
Затем в окне консоли нажмите следующую кнопку (справа)
а затем выберите Консоль Java Stack Trace из раскрывающегося списка.
Вставьте трассировку стека в консоль. Затем он предоставит список ссылок на ваш исходный код и любой другой доступный исходный код.
Вот что вы можете увидеть (изображение из документации Eclipse):

Самый последний сделанный вызов метода будет Топ стека, которая является верхней строкой (исключая текст сообщения). Спуск по стеку уходит в прошлое. Вторая строка — это метод, вызывающий первую строку и т. Д.
Если вы используете программное обеспечение с открытым исходным кодом, вам может потребоваться загрузить и прикрепить к своему проекту источники, если вы хотите изучить. Загрузите исходные jar-файлы, в своем проекте откройте Ссылки на библиотеки папку, чтобы найти банку для вашего модуля с открытым исходным кодом (тот, который содержит файлы классов), затем щелкните правой кнопкой мыши, выберите Свойства и прикрепите исходную банку.
Что такое трассировка стека и как я могу использовать ее для отладки ошибок моего приложения?
Иногда, когда я запускаю свое приложение, я получаю ошибку, которая выглядит примерно так:
Люди называют это «трассировкой стека». Что такое трассировка стека? Что она может сказать мне об ошибке в моей программе?
По поводу этого вопроса — довольно часто я вижу, как начинающий программист «получает ошибку» и просто вставляет свою трассировку стека и какой-то случайный блок кода, не понимая, что такое трассировка стека и как они могут используй это. Этот вопрос предназначен для начинающих программистов, которым может потребоваться помощь в понимании значения трассировки стека.
7 ответов
Проще говоря, трассировка стека — это список вызовов методов, которые приложение выполняло при возникновении исключения.
Простой пример
С помощью примера, приведенного в вопросе, мы можем точно определить, где в приложении возникло исключение. Посмотрим на трассировку стека:
Это очень простая трассировка стека. Если мы начнем с начала списка «в . », мы сможем сказать, где произошла наша ошибка. Мы ищем вызов самого верхнего метода, который является частью нашего приложения. В данном случае это:
Чтобы отладить это, мы можем открыть Book.java и посмотреть на строку 16 , которая:
Это будет означать, что что-то (вероятно, title ) есть null в приведенном выше коде.
Пример с цепочкой исключений
Иногда приложения перехватывают исключение и повторно генерируют его как причину другого исключения. Обычно это выглядит так:
Это может дать вам трассировку стека, которая выглядит так:
Что отличает этот, так это «Вызвано». Иногда исключения содержат несколько разделов «Причина». Для них обычно требуется найти «основную причину», которая будет одним из самых низких разделов «Причина» в трассировке стека. В нашем случае это:
Опять же, с этим исключением мы хотели бы взглянуть на строку 22 из Book.java , чтобы увидеть, что может вызвать здесь NullPointerException .
Более сложный пример с библиотечным кодом
Обычно трассировки стека намного сложнее, чем два приведенных выше примера. Вот пример (он длинный, но демонстрирует несколько уровней связанных исключений):
В этом примере многое другое. Что нас больше всего беспокоит, так это поиск методов, взятых из нашего кода , то есть чего угодно в пакете com.example.myproject . Во втором примере (выше) мы сначала хотели бы найти основную причину, а именно:
Однако все вызовы методов под этим кодом являются библиотечным кодом. Итак, мы перейдем к пункту «Причина» над ним и найдем первый вызов метода, исходящий из нашего кода, а именно:
Как и в предыдущих примерах, мы должны посмотреть на MyEntityService.java в строке 59 , потому что именно здесь возникла эта ошибка (это немного очевидно, что пошло не так, поскольку SQLException сообщает об ошибке, но процедура отладки что мы ищем).
Что такое Stacktrace?
Трассировка стека — очень полезный инструмент отладки. Он показывает стек вызовов (то есть стек функций, которые были вызваны до этого момента) в момент возникновения неперехваченного исключения (или время, когда трассировка стека была сгенерирована вручную). Это очень полезно, потому что показывает не только, где произошла ошибка, но и то, как программа оказалась в этом месте кода. Это приводит к следующему вопросу:
Что такое исключение?
Исключение — это то, что среда выполнения использует, чтобы сообщить вам, что произошла ошибка. Популярные примеры — NullPointerException, IndexOutOfBoundsException или ArithmeticException. Каждая из них возникает, когда вы пытаетесь сделать что-то, что невозможно. Например, при попытке разыменовать объект Null будет выброшено исключение NullPointerException:
Что делать с трассировками стека / исключениями?
Сначала выясните, что вызывает исключение. Попробуйте поискать в Google имя исключения, чтобы выяснить, в чем причина этого исключения. В большинстве случаев это вызвано неправильным кодом. В приведенных выше примерах все исключения вызваны неправильным кодом. Итак, для примера NullPointerException вы можете убедиться, что a никогда не имеет значения NULL в это время. Вы можете, например, инициализировать a или включить проверку, подобную этой:
Таким образом, нарушающая строка не выполняется, если a==null . То же самое и с другими примерами.
Иногда вы не можете быть уверены, что не получите исключения. Например, если вы используете сетевое соединение в своей программе, вы не можете помешать компьютеру потерять подключение к Интернету (например, вы не можете запретить пользователю отключать сетевое подключение компьютера). В этом случае сетевая библиотека, вероятно, выдаст исключение. Теперь вы должны перехватить исключение и обработать его. Это означает, что в примере с сетевым подключением вы должны попытаться повторно открыть соединение или уведомить пользователя или что-то в этом роде. Кроме того, всякий раз, когда вы используете catch, всегда перехватывайте только то исключение, которое хотите перехватить, не используйте общие операторы перехвата, такие как catch (Exception e) , которые перехватывали бы все исключения. Это очень важно, потому что в противном случае вы можете случайно поймать неправильное исключение и отреагировать неправильно.
Почему мне не следует использовать catch (Exception e) ?
Давайте воспользуемся небольшим примером, чтобы показать, почему не следует просто перехватывать все исключения:
Этот код пытается поймать ArithmeticException , вызванное возможным делением на 0. Но он также улавливает возможное NullPointerException , которое выбрасывается, если a или b являются null . Это означает, что вы можете получить NullPointerException , но вы будете рассматривать его как ArithmeticException и, вероятно, сделаете неправильный шаг. В лучшем случае вы все равно пропустите исключение NullPointerException. Подобные вещи значительно усложняют отладку, так что не делайте этого.
TL; DR
- Выясните, в чем причина исключения, и исправьте ее, чтобы исключение вообще не генерировалось.
- Если 1. невозможно, перехватите конкретное исключение и обработайте его.
- Никогда не добавляйте просто try / catch и игнорируйте исключение! Не делай этого!
- Никогда не используйте catch (Exception e) , всегда перехватывайте определенные исключения. Это избавит вас от головной боли.
Чтобы добавить к тому, что сказал Роб. Установка точек останова в вашем приложении позволяет выполнять пошаговую обработку стека. Это позволяет разработчику использовать отладчик, чтобы увидеть, в какой именно момент метод делает что-то неожиданное.
Поскольку Роб использовал NullPointerException (NPE), чтобы проиллюстрировать что-то общее, мы можем помочь устранить эту проблему следующим образом:
Если у нас есть метод, который принимает такие параметры, как: void (String firstName)
В нашем коде мы хотели бы оценить, что firstName содержит значение, мы бы сделали это так: if(firstName == null || firstName.equals(«»)) return;
Вышесказанное не позволяет нам использовать firstName в качестве небезопасного параметра. Поэтому, выполняя нулевые проверки перед обработкой, мы можем помочь убедиться, что наш код будет работать правильно. Чтобы расширить пример, в котором используется объект с методами, мы можем посмотреть здесь:
if(dog == null || dog.firstName == null) return;
Приведенный выше порядок является правильным для проверки наличия нулей, мы начинаем с базового объекта, в данном случае dog, а затем начинаем спускаться по дереву возможностей, чтобы убедиться, что все допустимо перед обработкой. Если бы порядок был изменен, NPE потенциально мог бы быть брошен, и наша программа вылетела бы.
Чтобы понять название : трассировка стека — это список исключений (или вы можете сказать список «Причина по»), от самого поверхностного исключения (например, исключение уровня сервиса) до самого глубокого ( например, исключение базы данных). Точно так же, как причина, по которой мы называем это «стеком», заключается в том, что стек является первым зашел последним (FILO), самое глубокое исключение произошло в самом начале, затем была сгенерирована цепочка исключений, серия последствий, поверхностное исключение было последним. одно произошло вовремя, но мы видим это в первую очередь.
Ключ 1 . Здесь необходимо понять сложную и важную вещь: самая глубокая причина может не быть «основной причиной», потому что, если вы напишете какой-то «плохой код», это может вызвать какое-то исключение ниже который глубже его слоя. Например, неверный sql-запрос может вызвать сброс соединения SQLServerException в нижней части вместо синтаксической ошибки, которая может быть только в середине стека.
-> Найдите основную причину в вашей работе.
Ключ 2 . Еще одна сложная, но важная вещь — внутри каждого блока «Причина по», первая строка была самым глубоким слоем и занимала первое место в этом блоке. Например,
Book.java:16 был вызван Auther.java:25, который был вызван Bootstrap.java:14, Book.java:16 был основной причиной. Здесь прикрепите диаграмму, отсортируйте стек трассировки в хронологическом порядке.
Есть еще одна функция трассировки стека, предлагаемая семейством Throwable — возможность манипулировать информацией трассировки стека.
Стандартное поведение:
Обработка трассировки стека:
Чтобы добавить к другим примерам, есть внутренние (вложенные) классы , которые отмечены знаком $ . Например:
Результатом будет эта трассировка стека:
В других сообщениях описывается, что такое трассировка стека, но с ней все равно сложно работать.
Если вы получили трассировку стека и хотите отследить причину исключения, хорошей отправной точкой для понимания этого является использование Java Stack Trace Console в Eclipse . Если вы используете другую IDE, может быть аналогичная функция, но этот ответ касается Eclipse.
Во-первых, убедитесь, что все ваши источники Java доступны в проекте Eclipse.
Затем в перспективе Java щелкните вкладку Консоль (обычно внизу). Если представление консоли не отображается, перейдите к пункту меню Окно -> Показать представление и выберите Консоль .
Затем в окне консоли нажмите следующую кнопку (справа)
А затем в раскрывающемся списке выберите Консоль трассировки стека Java .
Вставьте трассировку стека в консоль. Затем он предоставит список ссылок на ваш исходный код и любой другой доступный исходный код.
Вот что вы можете увидеть (изображение из документации Eclipse):

Самый последний сделанный вызов метода будет вершиной стека, то есть верхней строкой (за исключением текста сообщения). Спуск по стеку уходит в прошлое. Вторая строка — это метод, вызывающий первую строку и т. Д.
Если вы используете программное обеспечение с открытым исходным кодом, вам может потребоваться загрузить и прикрепить к своему проекту источники, если вы хотите изучить. Загрузите исходные jar-файлы в своем проекте, откройте папку Referenced Libraries , чтобы найти jar-файл для вашего модуля с открытым исходным кодом (тот, который содержит файлы классов), затем щелкните правой кнопкой мыши, выберите Properties и прикрепите исходный jar.
- NullPointerException if
objisnullin 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.0returnsinfinityjust fine! - NullPointerException if a
nullInteger is unboxed to anintin 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: