What Every Java Developer Should Know About Wrapping and Rethrowing Exceptions

adminguy's picture
Posted March 11th, 2015 by adminguy

     

 
 
 
 
 
 
 
In my experience as a Java developer, I have seen two ways Exceptions are propagated: the first is simple propagation where the exception simply passes through; and the second is propagating by wrapping the original exception. I'll first show you what each of these mean, with simple code stubs. Then we'll understand where each should be used.
 
Propagating an Exception by allowing it to pass through
 
public class Database {
     public String executeQuery(String query) throws DatabaseException {
        String data;
 
         // Fetch data from the database
         
         if(data == null ) {
             throw new DatabaseException("No such table Course");
        }
         return data;
    }
}
 
    
public static class Utils {
    public static String fetchSortedData(String query) throws DatabaseException {
        Database db = new Database();
        String data = db.executeQuery(query);           
     
            // sort the data and return sorted data
         return data;
    }
}
 
Code Example 1
  
 
Code Example 1 shows two classes: the first one Database represents a Database and has just one method to fetch data from queries. This method throws an Exception if the data could not be fetched. The second class Utils has one method fetchSortedData, which gets unsorted data from the Database class, and returns sorted data back to the user. This method has to deal with DatabaseException which could be thrown when it invoked db.executeQuery(...). Notice how it deals with the Exception by declaring it in it's own method signature, so the Exception gets propagated to the calling method up the call-stack. This is a very common strategy if you don't want to specifically catch the Exception. Just let it pass through.
 
 
Propagating an Exception by wrapping it
 
public class Database {
     public String executeQuery(String query) throws DatabaseException {
        String data;
 
         // Fetch data from the database
         
         if (data == null ) {
             throw new DatabaseException( "No such table Course");
        }
         return data;
    }
}
 
public static class Model {
 
     public String fetchDataList() throws ModelException {
        Database db = new Database();
        String data = null;
         try {
            data = db.executeQuery( "fetch * from Course");
        } catch(DatabaseException dbe) {
             throw new ModelException("Unable to fetch data", dbe);
        }
         return data;
    }
}
  
Code Example 2
 
In Code Example 1 we saw how the method Utils.fetchSortedData propagated DatabaseException by declaring it in its own method signature and allowing it to pass through up the call-stack. Code Example 2 on the other hand shows how to propagate an Exception by wrapping it.
 

The code below has three inner classes - View, Controller, and Model. We have made them inner classes for brevity. They represent the view layer, model layer, and controller respectively in real world software. The Model layer interacts with the Database, which is also encapsulated is an inner class.

When executed, you will notice that the main method invoked the controller controller.execute();. The controller needs to get data from the Model and give it to the View to be rendered. The Model invoked the executeQuery method on the Database, which for the purpose of our example is hard coded to always throw a DatabaseException. This Exception propagates to the fetchDataList() method in the Model. From a design perspective, it is not a good idea for the Model layer to propagate this Exception to the controller. This is because the Controller layer, or any layer above the Model layer should not have to know that we are dealing with a database. It is likely that at some point in the future we may shift to a totally new data storage mechanism. In such cases we should be able to seamlessly remove one layer (database) and replace it with another layer (some other data storage technology), without affecting layers that are higher up. However, if we expose them to DatabaseException, then such a transition will not be seamless.

Therefore, when an Exception crosses a layer boundary, it is a good idea to wrap the low level exception into an exception that is specific to that layer, and throw this new exception to the calling code.



When this code is run, an exception is thrown in the Database, wrapped in Model, and printed in Controller. The exception stack trace is shown below. You will notice that the stack trace has a Caused by: followed by another stack trace. This is an indication that the exception was wrapped.

 
Reading the stack trace of a wrapped Exception

EmbeddedStackTrace$ModelException: Unable to fetch data
    at EmbeddedStackTrace$Model.fetchDataList(EmbeddedStackTrace.java:38)
    at EmbeddedStackTrace$Controller.execute(EmbeddedStackTrace.java:22)
    at EmbeddedStackTrace.main(EmbeddedStackTrace.java:73)
Caused by: EmbeddedStackTrace$DatabaseException: No such table Course
    at EmbeddedStackTrace$Database.executeQuery(EmbeddedStackTrace.java:47)
    at EmbeddedStackTrace$Model.fetchDataList(EmbeddedStackTrace.java:36)
    ... 2 more