This Java exception tutorial helps you understand the concept of exception stack trace in Java and how to analyze an exception stack trace to detect bugs.
In this tutorial, we use the example in the article Understanding Java Exception Chaining with Code Examples. So kindly refer to that article while reading this one.
Look at the output of the StudentProgramexample:
StudentException: Error finding students at StudentManager.findStudents(StudentManager.java:13) at StudentProgram.main(StudentProgram.java:9) Caused by: DAOException: Error querying students from database at StudentDAO.list(StudentDAO.java:11) at StudentManager.findStudents(StudentManager.java:11) ... 1 more Caused by: java.sql.SQLException: Syntax Error at DatabaseUtils.executeQuery(DatabaseUtils.java:5) at StudentDAO.list(StudentDAO.java:8) ... 2 more
By examining this information we can find the root cause of the problem in order to fix bugs. In this exception stack trace, you see a list of chained exceptions which is sorted by the exception at the highest level to the one at the lowest level. This forms a stack like a stack of cards.
In each trace, we see the exception type (exception class name) along with the message:
StudentException: Error finding students
We also know the class, method and line number that raises the exception:
at StudentManager.findStudents(StudentManager.java:13)
This line tells us that the StudentException was thrown at line 13 in the method findStudents() of the class StudentManager.
And what causes the StudentException? Look at the next trace, we see:
Caused by: DAOException: Error querying students from database
That means the StudentException is caused by the DAOException which is thrown at line 11 in the method list() of the StudentDAO class.
Continue investigating further until the last exception in the trace, we see that the SQLException is actually the root cause and the actual place that sparks the exception is at line 5 in the method executeQuery() of the DatabaseUtils class:
Caused by: java.sql.SQLException: Syntax Error at DatabaseUtils.executeQuery(DatabaseUtils.java:5)
And here’s code of the DatabaseUtils class:
import java.sql.*; public class DatabaseUtils { public static void executeQuery(String sql) throws SQLException { throw new SQLException("Syntax Error"); } }
So basically that’s how we analyze the exception stack trace to find the root cause of the bug. The root cause is always at the bottom of the stack.
Chaining exceptions together is a good practice, as it prevents exceptions from losing in the stack trace. Let’s modify the StudentDAO class like this:
import java.sql.*; public class StudentDAO { public void list() throws DAOException { try { DatabaseUtils.executeQuery("SELECT"); } catch (SQLException ex) { throw new DAOException("Error querying students from database"); } } }
Here we remove the SQLExceptioninstance (ex) from the DAOException’s constructor. Let’s compile and run the StudentProgram again, we would get the following output:
StudentException: Error finding students at StudentManager.findStudents(StudentManager.java:13) at StudentProgram.main(StudentProgram.java:11) Caused by: DAOException: Error querying students from database at StudentDAO.list(StudentDAO.java:11) at StudentManager.findStudents(StudentManager.java:11) ... 1 more
By comparing this exception stack trace with the previous one, we see that the SQLException disappears, right? That means the SQLException is lost in the stack trace though it is actually the root cause. When this happens, it’s hard to detect bugs exactly as the truth is hidden.
So you understand the importance of chaining exceptions together, don’t you?
Besides the printStackTrace() method which prints out the detail exception stack trace, the Throwable class also provides several methods for working with the stack trace. Here I name a few.
} catch (StudentException ex) { try { PrintStream stream = new PrintStream(new File("exceptions1.txt")); ex.printStackTrace(stream); stream.close(); } catch (FileNotFoundException fne) { fne.printStackTrace(); } }
} catch (StudentException ex) { // print stack trace to a PrintWriter try { PrintWriter writer = new PrintWriter(new File("exceptions2.txt")); ex.printStackTrace(writer); writer.close(); } catch (FileNotFoundException fne) { fne.printStackTrace(); } }
} catch (StudentException ex) { StackTraceElement[] stackTrace = ex.getStackTrace(); for (StackTraceElement trace : stackTrace) { String traceInfo = trace.getClassName() + "." + trace.getMethodName() + ":" + trace.getLineNumber() + "(" + trace.getFileName() + ")"; System.out.println(traceInfo); } }
Consult the Javadoc of the Throwable class to see more methods like fillInStackTrace(), setStackTrace(), etc.
Don’t handle exceptions in the intermediate layers, because code in the middle layers is often used by code in the higher layers. It’s responsibility of the code in the top-most layer to handle the exceptions. The top-most layer is typically the user interface such as command-line console, window or webpage. And typically we handle exceptions by showing a warning/error message to the user.
This good practice is illustrated by the following picture:
So remember this rule when designing and coding your program.