Skip to main content

Explaining Java's error handling system

Posted by hellofadude on March 5, 2014 at 10:16 PM PST

In this post, I try to give a reasonable account of Java's error handling system being as it is that the handling of errors is a concern that any reasonable programming language must find some way to contend with. Java's error handling methodology is based on an idea of exceptions.

An exception is a condition that prevents your program from further executing along the current path. It signifies a problem in your program serious enough to require intervention because the system lacks sufficient information that would allow your program to proceed along current context. When your program encounters an exceptional condition, Java provides a mechanism that allows you to 'throw' an exception in a way that passes the problem to an exception handler designed for just such an eventuality.

Throwing an exception is a term that is used to describe the creation of a new object derived from the classes belonging to either one of two subtypes of the Throwable class i.e. the Error class - which are essentially a group of unchecked exceptions related to compile time and system errors which you are not required to declare in your exception specification, and the Exception class, most of whose subclasses with the exception of RuntimeException, belong to a category of checked exceptions you would normally be concerned to handle and which you would be required to declare in your exception specification. The exception specification refers to the part of your method appearing just after the argument list beginning with the throws keyword followed by a list of all potential exceptions types, essentially a way to alert users to the kind of exceptions your method is liable to throw.

Objects derived from RuntimeException are unchecked because they often represent exceptions that can be thrown during the normal operation of the Java Virtual Machine for instance, programmatic errors that may be beyond your control like an uninitialised variable could be an example of a run time exception. These kind of exceptions form a part of the standard Java runtime checking and as a consequence, you should not have to include them in your exception specification or worry about handling them.

Throwing exceptions

Throwing an exception in Java is simply a matter of making a call to the appropriate exception type after the throw keyword. For instance, you might throw an exception following a test for some particular condition like so:-

package errors.handling.java;

public class ThrowException {
     public static void main(String[] args) {
         String[] sequence = {"first element", "seccond element" };
         for(int i = 0; i < sequence.length; i++)
             //do something here
             System.out.println("In bound");
         throw new ArrayIndexOutOfBoundsException("sequence.length");
     }
}
/* Output
In bound
In bound
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
sequence.length
     at errors.handling.java.ThrowException.main(ThrowException.java:9)
*//

The above example reports an illegal index with the ArrayIndexOutOfBoundsException, with an optional string message in it's constructor argument. As you may observe, the name of exception classes in Java often implicitly describe the type of error and is often enough information to assist you to make an assessment of the problem.

When you throw an exception using the new keyword, an object is created on the heap in the normal way, however in addition to this, the current execution path is immediately terminated and the newly created object reference is ejected from the current context, in effect exiting the method just like you do with a return statement. At this moment Java's exception handling mechanism begins the task of locating an appropriate exception handler, usually in a higher context, from where the program may be recovered.

Exception handling

The handling of exceptions in Java constitute a two step process involving firstly, the capture of the exception and then the handling of it. Both stages work in concert to provide a robust exception handling model. To capture an exception you place exception generating code within whats called a "guarded region" - which consists of the try keyword followed by an ordinary scope:-

try {
//guarded region
} catch(Type1 ref1) { // exception handler for type1
//exception handling code goes here
} catch(Type2 ref2) { // exception handler for type 2
//exception handling code goes here
}

Immediately following the try block is the exception handler beginning with the catch keyword. The idea is to pass the appropriate exception type as argument to the catch block, such that a captured exception from the try block might be matched against it by the exception handling mechanism.

To properly appreciate Java's exception handling model, you must quickly become wise to the fact that more often than not, you will be concerned with handling other peoples thrown exceptions. As an example, consider for instance Java's FileReader class in the I/O package. This class is handy for reading streams of character from files but quite naturally has a constructor that throws a FileNotFoundException. If you wish to create an object of this class for use in your program, you will be required to handle this exception one way or another. The way to do that is to place the call to the constructor within a try/catch block like this:-

package errors.handling.java;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class HandleException {
     public static void main(String[] args) {
         String path = "/home/geekfest/eclipse/notice.html";
         File resource = new File(path);
         BufferedReader reader;
         try {
             reader = new BufferedReader(new FileReader(resource));
             try {
                 String console;
                 while((console = reader.readLine()) != null);
                 //System.out.println(console);
                 } catch(IOException e) {
                     System.out.println("read failed");
                 }
             } catch(FileNotFoundException e) {
                 System.out.println("File not found");
                 //exception handling code here
             }
         System.out.println("reader created successfully");
     }
}
/*
reader created successfully
*//

This example makes use of a nested try block to guard against multiple scenarios. First, we pass the static call to the FileReader constructor as an argument to create a reader within the first try block. Next, we read the contents of a file within a nested try block with the readLine() method which incidentally throws an IOException which is handled by the inner catch block. The outer catch block handles any exception thrown from the call to the FileReader constructor within the outer try block. We could just as easily have accomplished the same thing with a single try block and multiple catch blocks so long as we paid attention to the normal restrictions imposed by inheritance.

Of course the above example did not generate any exceptions so neither exception handler was ever really necessary. But the opposite might just have easily been the case if, for instance we made an error in our configuration or suffered some benign interruption to our I/O operation that caused it to fail.
The point to emphasise here is that you can define as many catch blocks as necessary to handle the many different exception types captured within your try block but you only need the one catch block to handle multiple instances of the same exception, all of this in addition to paying particular attention to the peculiar structure and semantics that come with the use of nested try blocks.

Irregardless, the exception handling mechanism will always make a search through the list of exception handlers in the order in which they are written and will enter into the first handler that is a match for a captured exception. Once a match is located, no further searching takes place as the particular exception is considered handled.
Note that as a result of inheritance, a match in this context does not have to be an exact match between the thrown exception object and the type of the exception handler; this is to say an exception handler with a base class argument will match a derived class object. For instance, a handler defined with the base Exception type argument will match an object of the derived type IOException, for this reason it is best practice to specify handlers of a more generalised type at the bottom of the list of exception handlers to avoid preempting any other more specific types that might be a closer match.

Sometimes, when dealing with programs significantly less complicated than of a commercial application, it is convenient to preserve your exceptions without regard to having to write a great deal of code by passing them out to console via your main() method. In this way you are precluded from having to write try/catch blocks within the body of your main():-

package errors.handling.java;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class ConsoleException {
     public static void main(String[] args) throws Exception {
         String path = "/home/geekfest/eclipse/notice.html";
         File resource = new File(path);
         BufferedReader reader = new BufferedReader(new FileReader(resource));
         String console;
         while((console = reader.readLine()) != null);
             //System.out.println(console);
             System.out.println("reader created successfully");
             reader.close();
     }
}
/* Output
reader created successfully
*//

In this version of a previous example, we use the exception specification syntax to specify that our main() throws the base Exception. This approach means we end up writing less code but is only really appropriate for use with very simple programs.

So what does it mean to handle an exception within the context of the code you write in the catch clause?
Normally, you would want to use the catch block to report the exception to the method caller. In the preceding example, if an exception was returned from the call to the FileReader constructor, it would have been caught in the associated catch clause and you would have had "Caught File Not Found Exception" printed to screen.
Reporting the exception is as much about notification as it is about providing pertinent information pertaining to the exception itself.
The Throwable class provides a number of methods with which one might query objects of it's subtypes for useful information pertaining to a thrown exception. For instance the getMessage() and getLocalisedMessage() methods both return a detailed and localised decription of the throwable object respectively, the printStackTrace(), printStackTrace(PrintStream) and printStackTrace(PrintWriter) - all of which print the Throwable and its backtrace to a stream of your choice - the no-argument constructor version prints to standard error. Another method, the fillInStackTrace() method records information within this throwable about the current state of stack frames. It is a useful practice to become familiar with the meaning inherent in the output returned from some of these methods to assist your troubleshooting efforts.

The stack trace

Of all the methods available to subtypes of the Throwable class, the printStackTrace() method is special because it tells you the exact point from whence this exception originated. This information could be very useful in locating the source of bugs in your code. You can use its cousin, the getStackTrace() method to return an array of stack trace elements that describe the sequence of method invocations leading up to the exception:-

package errors.handling.java;

public class StackTraceArray {
     static void first() {
         try {
             throw new Exception();
         } catch(Exception exceptionRef) {
             for(StackTraceElement traceElement : exceptionRef.getStackTrace())
                 System.out.println(traceElement.getMethodName());
         }
     }
     static void second() { first(); }
     static void last() { second(); }
     public static void main(String[] args) {
         first();
         System.out.println("\n" );
         second();
         System.out.println("\n");
         last();
     }
}
/* Output
first
main


first
second
main


first
second
last
main
*//

In this particular example, we throw and handle an exception of the base class Exception within our first() method and then progressively reach out to this method from within two other methods, a second() and last() method. In the exception handler, we use the for each syntax to loop through an array of stack trace elements returned by the getStackTrace(). The output displays the result of each method call from the main() and should be read as an array where the top of the stack equates to element zero in the array and represents the most recent method invocation in the sequence and typically the point from where the exception originated. In our little example we can easily see this to be the first() method. The last element in the array represents the bottom of the stack and is the oldest method invocation in the sequence.

Rethrowing exceptions

In addition to throwing an exception, you can rethrow an exception within a catch block for any number of reasons which may be convenient to your particular application. You may choose to rethrow the exception you have just caught or a different exception altogether. The key difference is when you rethrow an exception you have just caught, it ends up in an exception handler with all the original exception information intact, however, when you rethrow a different exception, you would normally be expected to lose all the information pertaining to the original exception which is in any case, replaced with information relating only to the new exception:-

package errors.handling.java;

public class RethrowingException {
     static void original() throws Exception {
         System.out.println("Throwing new exception from original()");
         throw new Exception("original Exception()");
     }
     static void throwOriginalException() throws Exception {
         System.out.println("Inside throwOriginalException():");
         try {
             original();
         } catch (Exception exceptionRef) {
             throw exceptionRef;
         }
     }
     static void throwNewException() throws Exception {
         System.out.println("Inside throwNewException()");
         try {
             original();
         } catch(Exception exceptionRef) {
             throw new Exception("new Exception()");
         }
     }
     public static void main(String[] args) {
         try {
             throwOriginalException();
         } catch(Exception exceptionRef) {
             System.out.println("in catch 1:");
             exceptionRef.printStackTrace(System.out);
         }
         try {
             throwNewException();
         } catch(Exception exceptionRef) {
             System.out.println("in catch 2:");
             exceptionRef.printStackTrace(System.out);
         }
     }
}
/* Output
Inside throwOriginalException():
Throwing new exception from original()
in catch 1:
java.lang.Exception: original Exception()
     at
errors.handling.java.RethrowingException.original(RethrowingException.java:6)
     at
errors.handling.java.RethrowingException.throwOriginalException(RethrowingException.java:11)
     at
errors.handling.java.RethrowingException.main(RethrowingException.java:26)
Inside throwNewException()
Throwing new exception from original()
in catch 2:
java.lang.Exception: new Exception()
     at
errors.handling.java.RethrowingException.throwNewException(RethrowingException.java:21)
     at
errors.handling.java.RethrowingException.main(RethrowingException.java:32)
*//

For this example, we call two methods in the main() both of which reference our original() method in turn, from which an exception is thrown. In the throwOriginalException() the caught exception is rethrown while a new exception is thrown in throwNewException(). The result in the output marked "in catch 2" is that any reference to the original exception is lost.

Chaining Exceptions

If you do not want to lose the information pertaining to the original exception while rethrowing a different exception, you can employ a technique called exception chaining to retain this information whereby you pass the original exception as a cause argument to the constructor of a Throwable object or objects of any of it's two main subclasses as well as an object of the RuntimeException class. For all other derived classes, you must use the initCause() method inherited from the Throwable class, that takes and returns a Throwable object which you can use to represent the cause:-

package errors.handling.java;

class NoSuchItemException extends Exception {}

class Item {
     String name;
     public Item(String item) {
         this.name = item;
     }
}
public class ShoppingCart {
     private Item[] cart;

     public ShoppingCart(String[] list) {
         cart = new Item[list.length];
         for(int i = 0; i < list.length; i++)
             cart[i] = new Item(list[i]);
     }

     public boolean checkItem(Item item) throws NoSuchItemException {
         if(item == null) {
             NoSuchItemException chainedException = new NoSuchItemException();
             // use initCause to wrap exception for
             //Throwable subclasses
             chainedException.initCause(new NullPointerException());
             throw chainedException;
         }
         for(int i = 0; i < cart.length; i++)
             if(cart[i].name.equals(item.name))
                 return true;
         return false;
     }
     public void addItem(Item item) {
         try {
             if(checkItem(item))
                 return;
             Item[] shelf = new Item[cart.length+1];
             for(int i = 0; i < cart.length; i++)
                 shelf[i] = cart[i]; // first make copy of array
             for(int i = cart.length; i < shelf.length; i++)
                 shelf[i] = item; // add item to array
             cart = shelf;
             } catch(NoSuchItemException noSuchItemRef) {
                 noSuchItemRef.printStackTrace(System.out);
                 }
     }
     public int equals(String it) {
         for(int i = 0; i < cart.length; i++)
             if(cart[i].name.equals(it))
             return i;
         return -1;
     }
     public int getItemIndex(Item item) throws IndexOutOfBoundsException {
         int index = this.equals(item.name);
         if(index == -1)
             throw new IndexOutOfBoundsException();
                 return index;
     }
     public Item[] getCart() {
         return cart;
     }
     public String toString() {
         StringBuilder displayCart = new StringBuilder();
         for(Item ref : cart) {
             displayCart.append(ref.name);
             displayCart.append("\n");
         }
         return displayCart.toString();
     }
     public static void main(String[] args) {
         String[] shoppinglist = { "Bread", "Milk", "Sugar" };
         ShoppingCart cart = new ShoppingCart(shoppinglist);
         System.out.println(cart);
         try {
             Item firstItem = new Item("Beans");
             Item secondItem = new Item("Tshirt");
             cart.addItem(firstItem);
             System.out.println(cart);
             cart.addItem(secondItem);
             System.out.println(cart);
             System.out.println(cart.getItemIndex(firstItem));
             System.out.println(cart.checkItem(secondItem));
             Item thirdItem = null;
             cart.addItem(thirdItem);
         } catch(NoSuchItemException noSuchItemRef) {
             noSuchItemRef.printStackTrace(System.out);
         } catch(IndexOutOfBoundsException outOfBoundsRef) {
             outOfBoundsRef.printStackTrace(System.out);
         }
     }
}
/* Output
Bread
Milk
Sugar

Bread
Milk
Sugar
Beans

Bread
Milk
Sugar
Beans
Tshirt

3
true
errors.handling.java.NoSuchItemException
     at errors.handling.java.ShoppingCart.checkItem(ShoppingCart.java:22)
     at errors.handling.java.ShoppingCart.addItem(ShoppingCart.java:35)
     at errors.handling.java.ShoppingCart.main(ShoppingCart.java:84)
Caused by: java.lang.NullPointerException
     at errors.handling.java.ShoppingCart.checkItem(ShoppingCart.java:25)
     ... 2 more
*//
*//

The example describes a ShoppingCart that is composed of an array of Item objects that are first initialised when a ShoppingCart object is created. You can then subsequently add additional items, check for an item or return the index of a specific item. To add an item, you first make a temporary copy of the array with length one longer than current array, append the item to that and then copy back to the original array in a way that is an abstraction of the process of selecting items from a shelf at your local supermarket. If you try adding a null value, the checkItem() method throws a custom exception distinct from the original NullPointerException which we pass to the initCause() method of the custom exception. You can see a NullPointerException is listed as the original cause of this exception in the output.

Turning off checked exceptions

Exception chaining also allows you to, in effect turn off checked exceptions by converting them to unchecked exceptions without losing any information about the original exception. You do this by wrapping a checked exception in the inside of a RuntimeException like so:-

package errors.handling.java;

import java.io.IOException;

public class TurnOffException {
      static void throwAnException()  {
         System.out.println("Throwing new exception from throwAnException()");
         try {
             throw new IOException();
         } catch(IOException ioExceptionRef) {
             throw new RuntimeException(ioExceptionRef);
         }
     }
     public static void main(String[] args) {
         try {
             TurnOffException.throwAnException();
         } catch(RuntimeException runtimeExceptionRef) {
             try {
                 throw runtimeExceptionRef.getCause();
             } catch(IOException ioExceptionRef) {
                 System.out.println("IOException " + ioExceptionRef);
             }catch(Throwable throwableRef) {
                 System.out.println("Throwable " + throwableRef);
             }
         }
         //can call method without try block
         TurnOffException.throwAnException();
     }
}
/* Output
Throwing new exception from throwAnException()
IOException java.io.IOException
Throwing new exception from throwAnException()
Exception in thread "main" java.lang.RuntimeException: java.io.IOException
     at
errors.handling.java.TurnOffException.throwAnException(TurnOffException.java:11)
     at errors.handling.java.TurnOffException.main(TurnOffException.java:27)
Caused by: java.io.IOException
     at
errors.handling.java.TurnOffException.throwAnException(TurnOffException.java:9)
     ... 1 more
*//

This technique effectively allows you to ignore the checked exception relieving you of the need of having to write try/catch clauses and/or the usual exception specification.

Throwing custom exceptions

You might sometimes find it necessary to create your own exceptions to handle errors that might be peculiar to your own library by inheriting from an existing exception class, ideally an exception that more closely matches in meaning of your own exception:-

package errors.handling.java;

import java.util.Random;

class CustomException extends Exception {
     public CustomException(String message) {
         System.out.println(message);
     }
}
public class ThrowAnException {
     public void throwCustomException(int condition)  throws CustomException {
         if(condition == 5)
             throw new CustomException("Just got ejected!");
         System.out.println("condition = " + condition);
         //some code
     }
     public static void main(String[] args) {
         ThrowAnException objRef = new ThrowAnException();
         try {
             for(int i = 0; i < 100; i++)
                 objRef.throwCustomException(new Random().nextInt(6));
         } catch(CustomException customExceptionRef) {
             System.out.println("Caught it!");
         }
     }
}
/* Output
i = 3
i = 0
i = 3
Just got ejected!
Caught it!
*//

Here, we inherit from the base Exception class to create a CustomException that is thrown in the main whenever condition == 5. The method throwCustomException() uses the the exception specification syntax to describe it's potential to throw a CustomException. Whenever we come across a method that uses the exception specification to declare a list of potential exceptions, this is the clue that we must handle this exception whenever we call the method.

In true OOP tradition, inheritance further imposes other constraints on derived classes with respect to the exception specification. For instance, when you override a method that defines an exception specification, the derived method can only throw those exceptions that have been specified in the base method, or exceptions derived from those specified in the base method, or it can choose not to throw any methods at all, but it can not throw a different set of exceptions to the one in the base class. With constructors however, this restriction is partially eased in a sense because even though a derived class must somehow account for exceptions thrown by the base class constructor, it is free to throw additional exceptions. Lastly, when you implement an interface in a derived class, methods inherited from that interface can not be expected to override those inherited from the base class.

The finally statement

The finally clause is used to perform what is commonly referred to as 'clean up' operations in Java. These are the type of operations you would normally want your program to execute irregardless of whether or not an exception is thrown. The finally clause is normally placed at the end of a try/catch block. The kinds of operation you would normally want to place within a finally clause include code to shut out a network connection after you no longer have any use for it, or close a file once your program has successfully read it's contents or some other such operation. In a sense, the finally clause allows you to delay the termination of the current execution path following a thrown exception, just long enough to execute some code.

In the following example, a FileResource class uses a FileReader and a BufferedReader object from the Java standard I/O library to open and read a file. Java's API reference describes the FileReader class constructor as possibly throwing a FileNotFoundException.
The FileResource has a read() method to read the file and a close() to perform clean up at the end of the object's lifetime. The close() method of this class calls the close() method of the BufferedReader object to close the stream and release any system resources associated with it:-

package errors.handling.java;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

class FileResource {
     private BufferedReader reader;
     String path = "/home/geekfest/eclipse/notice.html";
     File resource = new File(path);
     public FileResource() throws FileNotFoundException {
         try {
             reader = new BufferedReader(new FileReader(resource));
         } catch(FileNotFoundException ref) {
             System.out.println("unable to open file " + resource);
             ref.printStackTrace();
         } finally {
             //Don't close here
         }
     }
     public String read() {
         String output = null;
         try {
             output = reader.readLine();
         } catch(IOException ref) {
             ref.printStackTrace(System.out);
         }
         return output;
     }
     public void close() {
         try {
             reader.close();
             System.out.println("close() successful");
         } catch(IOException ref) {
             ref.printStackTrace(System.out);
         }
     }
}
public class ConsoleDevice {
     public static void main(String[] args) {
         try {
             FileResource resource = new FileResource();
             try {
                 String console;
                 while((console = resource.read()) != null);
                     //System.out.println(console);
             } catch(Exception ref) {
                 ref.printStackTrace(System.out);
             } finally {
                 resource.close();
             }
         } catch(Exception ref) {
             ref.printStackTrace(System.out);
       }

     }
}
/* Output
close() successful
*//

In the main() of the ConsoleDevice class we use a set of nested try blocks to safely create and use the FileResource class to read text from a file unto our console object. Following the while loop in the inner try block, we call the close() method in the finally clause to perform clean up, at the only point in the program we can be certain clean up will be necessary.

try-with-resources statement

With Java SE 7 came the introduction of the try-with-resources statement to ensure that each resource is automatically closed at the end of a statement. A resource includes any object that implements AutoClosable. The following example demonstrates the the use of the try-with-resources statement with out FileResource class:-

package errors.handling.java;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

class FileResource implements AutoCloseable {
     private BufferedReader reader;
     String path = "/home/geekfest/eclipse/notice.html";
     File resource = new File(path);
     public FileResource() throws FileNotFoundException {
         try {
             reader = new BufferedReader(new FileReader(resource));
         } catch(FileNotFoundException ref) {
             System.out.println("unable to open file " + resource);
             ref.printStackTrace();
         } finally {
             //Don't close here
         }
     }
     public String read() {
         String output = null;
         try {
             output = reader.readLine();
         } catch(IOException ref) {
             ref.printStackTrace(System.out);
         }
         return output;
     }
     public void close() {
         try {
             reader.close();
             System.out.println("close() successful");
         } catch(IOException ref) {
             ref.printStackTrace(System.out);
         }
     }
}
public class ConsoleDevice2 {
     public static void main(String[] args) throws FileNotFoundException {
     try (FileResource resource = new FileResource();) {
             String console;
             while((console = resource.read()) != null);
             //System.out.println(console);
         }

     }
}
/* Output
close() successful
*//

In example, the try-with-resources statement is implemented in the main() by means of a call to our FileResource class within the parenthesis that appear following the try keyword. Our FileResource class has had to be modified to implement the java.lang.AutoCloseable interface. With the try-with-resources statement, you do not need to include a finally statement because the resource will be closed regardless of whether the try statement fails to complete. In addition, exceptions thrown from from within the try block will supercede those thrown from the try-with-resources statements it self which become suppressed. You can include one or more resources in a try-with-resources statement declaration, by separating each call with a semi-colon. Each resources' close() method will be called in the opposite order of their creation.