The Java Exception Hierarchy – Unchecked and Checked Exceptions

All Java exceptions are instances of classes in the Exception class hierarchy. This can be represented as follows:

  • java.lang.Throwable – This is the base class for all exception classes. Its methods and constructors implement a range of functionality common to all exceptions.
    • java.lang.Exception – This is the superclass of all normal exceptions.
      • various standard and custom exception classes.
      • java.lang.RuntimeException – This the superclass of all normal exceptions that are unchecked exceptions.
        • various standard and custom runtime exception classes.
    • java.lang.Error – This is the superclass of all “fatal error” exceptions.

Notes:

  1. The distinction between checked and unchecked exceptions is described below.
  2. The Throwable, Exception and RuntimeException class should be treated as abstract; see Pitfall – Throwing
    Throwable, Exception, Error or RuntimeException.
  3. The Error exceptions are thrown by the JVM in situations where it would be unsafe or unwise for an application to attempt to recover.
  4. It would be unwise to declare custom subtypes of Throwable. Java tools and libraries may assume that Error and Exception are the only direct subtypes of Throwable, and misbehave if that assumption is incorrect.

Checked versus Unchecked Exceptions

One of the criticisms of exception support in some programming languages is that is difficult to know which exceptions a given method or procedure might throw. Given that an unhandled exception is liable to cause a
program to crash, this can make exceptions a source of fragility.

The Java language addresses this concern with the checked exception mechanism. First, Java classifies exceptions into two categories:

  • Checked exceptions typically represent anticipated events that an application should be able to deal with. For instance, IOException and its subtypes represent error conditions that can occur in I/O operations. Examples include, file opens failing because a file or directory does not exist, network reads and writes failing because a network connection has been broken and so on.
  • Unchecked exceptions typically represent unanticipated events that an application cannot deal with. These are typically the result of a bug in the application.

(In the following, “thrown” refers to any exception thrown explicitly (by a throw statement), or implicitly (in a failed dereference, type cast and so on). Similarly, “propagated” refers to an exception that was thrown in a nested call, and not caught within that call. The sample code below will illustrate this.)

The second part of the checked exception mechanism is that there are restrictions on methods where a checked
exception may occur:

When a checked exception is thrown or propagated in a method, it must either be caught by the method, or
listed in the method’s throws clause. (The significance of the throws clause is described in this example.)
When a checked exception is thrown or propagated in an initializer block, it must be caught the the block.
A checked exception cannot be propagated by a method call in a field initialization expression. (There is no
way to catch such an exception.)

In short, a checked exception must be either handled, or declared.

These restrictions do not apply to unchecked exceptions. This includes all cases where an exception is thrown implicitly, since all such cases throw unchecked exceptions.

Checked exception examples

These code snippets are intended to illustrate the checked exception restrictions. In each case, we show a version of the code with a compilation error, and a second version with the error corrected.

// This declares a custom checked exception.
public class MyException extends Exception {
     // constructors omitted.
}

// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
     // constructors omitted.
}

The first example shows how explicitly thrown checked exceptions can be declared as “thrown” if they should not be handled in the method.

// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
    int i = 1 / 0; // Compiles OK, throws ArithmeticException
    if (flag) {
         throw new MyException(); // Compilation error
    } else {
         throw new MyException2(); // Compiles OK
    }
}

// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
     int i = 1 / 0; // Compiles OK, throws ArithmeticException
     if (flag) {
         throw new MyException(); // Compilation error
     } else {
         throw new MyException2(); // Compiles OK
     }
}

The second example shows how a propagated checked exception can be dealt with.

// INCORRECT
public void methodWithPropagatedCheckedException() {
      InputStream is = new FileInputStream("someFile.txt"); // Compilation error
      // FileInputStream throws IOException or a subclass if the file cannot
      // be opened. IOException is a checked exception.
…
}

// CORRECTED (Version A)
public void methodWithPropagatedCheckedException() throws IOException {
     InputStream is = new FileInputStream("someFile.txt");
…
}

// CORRECTED (Version B)
public void methodWithPropagatedCheckedException() {
     try {
         InputStream is = new FileInputStream("someFile.txt");
         …
     } catch (IOException ex) {
         System.out.println("Cannot open file: " + ex.getMessage());
     }
}

The final example shows how to deal with a checked exception in a static field initializer.

// INCORRECT
public class Test {
     private static final InputStream is =
          new FileInputStream("someFile.txt"); // Compilation error
}

// CORRECTED
public class Test {
      private static final InputStream is;
      static {
            InputStream tmp = null;
            try {
                tmp = new FileInputStream("someFile.txt");
            } catch (IOException ex) {
                System.out.println("Cannot open file: " + ex.getMessage());
            }
            is = tmp;
     }
}

Note that in this last case, we also have to deal with the problems that is cannot be assigned to more than once, and yet also has to be assigned to, even in the case of an exception.

Creating and reading stacktraces

When an exception object is created (i.e. when you new it), the Throwable constructor captures information about the context in which the exception was created. Later on, this information can be output in the form of a stacktrace, which can be used to help diagnose the problem that caused the exception in the first place.

Printing a stacktrace

Printing a stacktrace is simply a matter of calling the printStackTrace() method. For example:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // This prints the stacktrace to standard output
    ex.printStackTrace();
}

The printStackTrace() method without arguments will print to the application’s standard output; i.e. the current System.out. There are also printStackTrace(PrintStream) and printStackTrace(PrintWriter) overloads that print to a specified Stream or Writer.

Notes:

  • The stacktrace does not include the details of the exception itself. You can use the toString() method to get those details; e.g.
// Print exception and stacktrace
System.out.println(ex);
ex.printStackTrace();
  • Stacktrace printing should be used sparingly; see Pitfall – Excessive or inappropriate stacktraces . It is often better to use a logging framework, and pass the exception object to be logged.

Understanding a stacktrace

Consider the following simple program consisting of two classes in two files. (We have shown the filenames and added line numbers for illustration purposes.)

File: "Main.java"
1     public class Main {
2          public static void main(String[] args) {
3              new Test().foo();
4          }
5     }
File: "Test.java"
1      class Test {
2            public void foo() {
3                   bar();
4            }
5
6            public int bar() {
7                 int a = 1;
8                 int b = 0;
9                 return a / b;
10           }

When these files are compiled and run, we will get the following output.

Exception in thread "main" java.lang.ArithmeticException: / by zero
at Test.bar(Test.java:9)
at Test.foo(Test.java:3)
at Main.main(Main.java:3)

Let us read this one line at a time to figure out what it is telling us.

Line #1 tells us that the thread called “main” has terminated due to an uncaught exception. The full name of the exception is java.lang.ArithmeticException, and the exception message is “/ by zero”.

If we look up the javadocs for this exception, it says:

Thrown when an exceptional arithmetic condition has occurred. For example, an integer “divide by zero” throws an instance of this class.

Indeed, the message “/ by zero” is a strong hint that the cause of the exception is that some code has attempted to divide something by zero. But what?

The remaining 3 lines are the stack trace. Each line represents a method (or constructor) call on the call stack, and each one tells us three things:

  • the name of the class and method that was being executed,
  • the source code filename,
  • the source code line number of the statement that was being executed

These lines of a stacktrace are listed with the frame for the current call at the top. The top frame in our example above is in the Test.bar method, and at line 9 of the Test.java file. That is the following line:

return a / b;

If we look a couple of lines earlier in the file to where b is initialized, it is apparent that b will have the value zero. We can say without any doubt that this is the cause of the exception.

If we needed to go further, we can see from the stacktrace that bar() was called from foo() at line 3 of Test.java, and that foo() was in turn called from Main.main().

Note: The class and method names in the stack frames are the internal names for the classes and methods. You will need to recognize the following unusual cases:

  • A nested or inner class will look like “OuterClass$InnerClass”.
  • An anonymous inner class will look like “OuterClass$1”, “OuterClass$2”, etcetera.
  • When code in a constructor, instance field initializer or an instance initializer block is being executed, the method name will be “”.
  • When code in a static field initializer or static initializer block is being executed, the method name will be “”.

(In some versions of Java, the stacktrace formatting code will detect and elide repeated stackframe sequences, as can occur when an application fails due to excessive recursion.)

Exception chaining and nested stacktraces
Version ≥ Java SE 1.4

Exception chaining happens when a piece of code catches an exception, and then creates and throws a new one, passing the first exception as the cause. Here is an example:

File: Test,java
1     public class Test {
2        int foo() {
3             return 0 / 0;
4        }
5
6        public Test() {
7             try {
8                 foo();
9             } catch (ArithmeticException ex) {
10                throw new RuntimeException("A bad thing happened", ex);
11            }
12     }
13
14      public static void main(String[] args) {
15            new Test();
16      }
17   }

When the above class is compiled and run, we get the following stacktrace:

Exception in thread "main" java.lang.RuntimeException: A bad thing happened
     at Test.(Test.java:10)
     at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
     at Test.foo(Test.java:3)
     at Test.(Test.java:8)
     … 1 more

The stacktrace starts with the class name, method and call stack for the exception that (in this case) caused the application to crash. This is followed by a “Caused by:” line that reports the cause exception. The class name and
message are reported, followed by the cause exception’s stack frames. The trace ends with an “… N more” which indicates that the last N frames are the same as for the previous exception.

The “Caused by:” is only included in the output when the primary exception’s cause is not null). Exceptions can be chained indefinitely, and in that case the stacktrace can have multiple “Caused by:” traces.

Note: the cause mechanism was only exposed in the Throwable API in Java 1.4.0. Prior to that, exception chaining needed to be implemented by the application using a custom exception field to represent the cause, and a custom printStackTrace method.

Capturing a stacktrace as a String

Sometimes, an application needs to be able to capture a stacktrace as a Java String, so that it can be used for other purposes. The general approach for doing this is to create a temporary OutputStream or Writer that writes to an inmemory buffer and pass that to the printStackTrace(…).

The Apache Commons and Guava libraries provide utility methods for capturing a stacktrace as a String:

org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

com.google.common.base.Throwables.getStackTraceAsString(Throwable)

If you cannot use third party libraries in your code base, then the following method with do the task:

    /**
    * Returns the string representation of the stack trace.
    * @param throwable the throwable
    * @return the string.
    * /
    public static String stackTraceToString(Throwable throwable)      {
      StringWriter stringWriter = new StringWriter();
      throwable.printStackTrace(new PrintWriter(stringWriter));
      return stringWriter.toString();
      }

Note that if your intention is to analyze the stacktrace, it is simpler to use getStackTrace() and getCause() than to attempt to parse a stacktrace.

Throwing an exception

The following example shows the basics of throwing an exception:

public void checkNumber(int number) throws IllegalArgumentException {
     if (number < 0) {
          throw new IllegalArgumentException("Number must be positive: " + number);
     }
}

The exception is thrown on the 3rd line. This statement can be broken down into two parts:

  • new IllegalArgumentException(…) is creating an instance of the IllegalArgumentException class, with a message that describes the error that exception is reporting.
  • throw … is then throwing the exception object.

When the exception is thrown, it causes the enclosing statements to terminate abnormally until the exception is handled. This is described in other examples.

It is good practice to both create and throw the exception object in a single statement, as shown above. It is also good practice to include a meaningful error message in the exception to help the programmer to understand the
cause of the problem. However, this is not necessarily the message that you should be showing to the end user. (For a start, Java has no direct support for internationalizing exception messages.)

There are a couple more points to be made:

  • We have declared the checkNumber as throws IllegalArgumentException. This was not strictly necessary, since IllegalArgumentException is a checked exception; see The Java Exception Hierarchy – Unchecked and Checked Exceptions. However, it is good practice to do this, and also to include the exceptions thrown a method’s javadoc comments.
  • Code immediately after a throw statement is unreachable. Hence if we wrote this:
throw new IllegalArgumentException("it is bad");
return;

the compiler would report a compilation error for the return statement.

Exception chaining

Many standard exceptions have a constructor with a second cause argument in addition to the conventional message argument. The cause allows you to chain exceptions. Here is an example.

First we define an unchecked exception that our application is going throw when it encounters a non-recoverable error. Note that we have included a constructor that accepts a cause argument.

     public class AppErrorException extends RuntimeException {
          public AppErrorException() {
               super();
          }

          public AppErrorException(String message) {
               super(message);
          }

          public AppErrorException(String message, Throwable cause) {
               super(message, cause);
          }
}

Next, here is some code that illustrates exception chaining.

public String readFirstLine(String file) throws AppErrorException {
      try (Reader r = new BufferedReader(new FileReader(file))) {
           String line = r.readLine();
           if (line != null) {
                return line;
           } else {
                throw new AppErrorException("File is empty: " + file);
           }
      } catch (IOException ex) {
           throw new AppErrorException("Cannot read file: " + file, ex);
      }
}

The throw within the try block detects a problem and reports it via an exception with a simple message. By
contrast, the throw within the catch block is handling the IOException by wrapping it in a new (checked) exception.
However, it is not throwing away the original exception. By passing the IOException as the cause, we record it so
that it can be printed in the stacktrace, as explained in Creating and reading stacktraces.

Advanced features of Exceptions

This example covers some advanced features and use-cases for Exceptions.

Examining the callstack programmatically
Version ≥ Java SE 1.4

The primary use of exception stacktraces is to provide information about an application error and its context so that the programmer can diagnose and fix the problem. Sometimes it can be used for other things. For example, a
SecurityManager class may need to examine the call stack to decide whether the code that is making a call should be trusted.

You can use exceptions to examine the call stack programmatically as follows:

Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());

There are some important caveats on this:

  1. The information available in a StackTraceElement is limited. There is no more information available than is displayed by printStackTrace. (The values of the local variables in the frame are not available.)
  2. The javadocs for getStackTrace() state that a JVM is permitted to leave out frames:

Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this throwable is permitted to return a zero-length array from this method.

Optimizing exception construction

As mentioned elsewhere, constructing an exception is rather expensive because it entails capturing and recording information about all stack frames on the current thread. Sometimes, we know that that information is never going to be used for a given exception; e.g. the stacktrace will never be printed. In that case, there is an implementation trick that we can use in a custom exception to cause the information to not be captured.

The stack frame information needed for stacktraces, is captured when the Throwable constructors call the Throwable.fillInStackTrace() method. This method is public, which means that a subclass can override it. The trick is to override the method inherited from Throwable with one that does nothing; e.g.

public class MyException extends Exception {
     // constructors

     @Override
     public void fillInStackTrace() {
          // do nothing
     }
}

The problem with this approach is that an exception that overrides fillInStackTrace() can never capture the stacktrace, and is useless in scenarios where you need one.

Erasing or replacing the stacktrace
Version ≥ Java SE 1.4

In some situations, the stacktrace for an exception created in the normal way contains either incorrect information, or information that the developer does not want to reveal to the user. For these scenarios, the Throwable.setStackTrace can be used to replace the array of StackTraceElement objects that holds the information.

For example, the following can be used to discard an exception’s stack information:

exception.setStackTrace(new StackTraceElement[0]);

Suppressed exceptions
Version ≥ Java SE 7

Java 7 introduced the try-with-resources construct, and the associated concept of exception suppression. Consider the following snippet:

try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
      // do stuff
     int temp = 0 / 0; // throws an ArithmeticException
}

When the exception is thrown, the try will call close() on the w which will flush any buffered output and then close the FileWriter. But what happens if an IOException is thrown while flushing the output?

What happens is that any exception that is thrown while cleaning up a resource is suppressed. The exception is caught, and added to the primary exception’s suppressed exception list. Next the try-with-resources will continue with the cleanup of the other resources. Finally, primary exception will be rethrown.

A similar pattern occurs if an exception it thrown during the resource initialization, or if the try block completes normally. The first exception thrown becomes the primary exception, and subsequent ones arising from cleanup are suppressed.

The suppressed exceptions can be retrieved from the primary exception object by calling getSuppressedExceptions.

The try-finally and try-catch-finally statements

The try…catch…finally statement combines exception handling in java with clean-up code. The finally block contains code that will be executed in all circumstances. This makes them suitable for resource management, and
other kinds of cleanup.

Try-finally

Here is an example of the simpler (try…finally) form:

try {
    doSomething();
} finally {
    cleanUp();
}

The behavior of the try…finally is as follows:

  • The code in the try block is executed.
  • If no exception was thrown in the try block:
    • The code in the finally block is executed.
    • If the finally block throws an exception, that exception is propagated.
    • Otherwise, control passes to the next statement after the try…finally.
  • If an exception was thrown in the try block:
    • The code in the finally block is executed.
    • If the finally block throws an exception, that exception is propagated.
    • Otherwise, the original exception continues to propagate.

The code within finally block will always be executed. (The only exceptions are if System.exit(int) is called, or if the JVM panics.) Thus a finally block is the correct place code that always needs to be executed; e.g. closing files
and other resources or releasing locks.

try-catch-finally

Our second example shows how catch and finally can be used together. It also illustrates that cleaning up resources is not straightforward.

// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
     reader = new BufferedReader(new FileReader(fileName));
     result = reader.readLine();
} catch (IOException ex) {
     Logger.getLogger.warn("Unexpected IO error", ex); // logging the exception
} finally {
     if (reader != null) {
           try {
                reader.close();
           } catch (IOException ex) {
                // ignore / discard this exception
           }
     }
}

The complete set of (hypothetical) behaviors of try…catch…finally in this example are too complicated to describe here. The simple version is that the code in the finally block will always be executed.

Looking at this from the perspective of resource management:

  • We declare the “resource” (i.e. reader variable) before the try block so that it will be in scope for the finally block.
  • By putting the new FileReader(…), the catch is able to handle any IOError exception from thrown when opening the file.
  • We need a reader.close() in the finally block because there are some exception paths that we cannot intercept either in the try block or in catch block.
  • However, since an exception might have been thrown before reader was initialized, we also need an explicit null test.
  • Finally, the reader.close() call might (hypothetically) throw an exception. We don’t care about that, but if we don’t catch the exception at source, we would need to deal with it further up the call stack.

Version ≥ Java SE 7
Java 7 and later provide an alternative try-with-resources syntax which significantly simplifies resource clean-up.

The ‘throws’ clause in a method declaration

Java’s checked exception mechanism requires the programmer to declare that certain methods could throw specifed checked exceptions. This is done using the throws clause. For example:

public class OddNumberException extends Exception { // a checked exception
}

public void checkEven(int number) throws OddNumberException {
     if (number % 2 != 0) {
        throw new OddNumberException();
     }
}

The throws OddNumberException declares that a call to checkEven could throw an exception that is of type OddNumberException.

A throws clause can declare a list of types, and can include unchecked exceptions as well as checked exceptions.

public void checkEven(Double number)
         throws OddNumberException, ArithmeticException {
    if (!Double.isFinite(number)) {
         throw new ArithmeticException("INF or NaN");
    } else if (number % 2 != 0) {
         throw new OddNumberException();
    }
}

What is the point of declaring unchecked exceptions as thrown?

The throws clause in a method declaration serves two purposes:

  1. It tells the compiler which exceptions are thrown so that the compiler can report uncaught (checked) exceptions as errors.
  2. It tells a programmer who is writing code that calls the method what exceptions to expect. For this purpose, it often makes to senses to include unchecked exceptions in a throws list.

Note: that the throws list is also used by the javadoc tool when generating API documentation, and by a typical IDE’s “hover text” method tips.

Throws and method overriding

The throws clause forms part of a method’s signature for the purpose of method overriding. An override method can be declared with the same set of checked exceptions as thrown by the overridden method, or with a subset. However the override method cannot add extra checked exceptions. For example:

@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an
unchecked exception
…

@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
…

class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}

@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these
are both subclasses

@Override
public void checkEven(Double number) throws IOExcepion // ERROR

The reason for this rule is that if an overridden method can throw a checked exception that the overridden method could not throw, that would break type substitutability.

LEAVE A REPLY

Please enter your comment!
Please enter your name here