ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


An Exception Handling Framework for J2EE Applications

by ShriKant Vashishtha
01/11/2006

In most Java projects, a large percentage of the code is boilerplate code. Exception handling comes under this category. Even though the business logic may be just three or four lines of code, exception handling might go on for ten to 20 lines. This article talks about how to keep exception handling simple and straightforward, keeping the developer's plate clean for him to concentrate on business logic rather than devoting time to writing exception-handling boilerplate code. It also gives the basis and guidelines to create and deal with exceptions in the J2EE environment and targets some of the business problems, in which exceptions could be used to resolve them. This article uses the Struts framework as the presentation implementation, though the approach is applicable to any presentation implementation.

When Do We Need Checked and Unchecked Exceptions?

Have you ever wondered why it is such a pain to put a try-catch block around a block of code you have written, even if you know that you cannot do much about those exceptions and will be content with just logging them in the catch block? You may wonder why this can't just be logged in a centralized place, which in most cases for a J2EE application is a front controller. In other words, you would like to not be bothered with them, as you don't have much to do with them. But what if a method signature contains a throws clause? You are either forced to catch these exceptions or put them in a throws clause of your own method. That's a pain! Fortunately, the Java APIs have a category of exceptions called unchecked exceptions, which you are not forced to catch. Still, the question is, on what basis do you decide which exceptions should be checked and which unchecked? Here are some guidelines:

Related Reading

Java Enterprise Best Practices
By The O'Reilly Java Authors

Exception Throwing Strategy

Catch a base application exception (say, BaseAppException) only and declare in your throws clause

In most of J2EE applications, decisions about what error message to show on which screen against an exception get made in the presentation layer only. This brings up another question: why shouldn't we put this decision making in a common place? In J2EE applications, a front controller is a centralized place to do common handling.

Also, there has to be a common mechanism to propagate the exceptions. Exceptions need to be handled in a generic way, too. To deal with this, we always need to catch the base application exception BaseAppException at the controller end. This means we need to put the BaseAppException, and only that exception, in the throws clause of each method that could throw a checked exception. The concept is to use polymorphism to hide the actual implementation of the exception. We just catch BaseAppException in the controller, but the specific exception instance thrown might be any of several derived exception classes. You get a lot of exception-handling flexibility using this approach:

Here is a sample of throwing a checked exception.

public void updateUser(UserDTO userDTO) 
  throws BaseAppException{
    UserDAO userDAO = new UserDAO();
    UserDAO.updateUser(userDTO);
    ...
    if(...)
        throw new RegionNotActiveException(
            "Selected region is not active");
}

Controller Method:
...
try{
    User user = new User();
    user.updateUser(userDTO);
}catch(BaseAppException ex){
    //ExceptionHandler is used to handle
    //all exceptions derived from BaseAppException
}
...

So far we have discussed that all methods that could potentially throw checked exceptions and are called by Controller should contain only BaseAppException in their throws clauses. However, this actually implies that we can't have any other application exception in the throws clause. But what if you need to perform some business logic based on a certain type of exception in the catch block? For handling those cases, a method may also throw a specific exception. Keep in mind that this is a special case and should not be taken for granted by developers. The application exception in question again should extend the BaseAppException. Here is an example:

CustomerDAO method:
//throws CustomerNotActiveException along with
//BaseAppException
public CustomerDTO getCustomer(InputDTO inputDTO)
    throws BaseAppException,
        CustomerNotActiveException {
    . . .
    //Make a DB call to fetch the customer 
    //details based on inputDTO
    . . .
    // if not details found
    throw new CustomerNotActiveException(
        "Customer is not active");
}

Client method:

//catch CustomerNotActiveException
//and continues its processing
public CustomerDTO getCustomerDetails(
  UserDTO userDTO)
    throws BaseAppException{
    ...
    CustomerDTO custDTO = null;
    try{
        //Get customer details 
        //from local database
        customerDAO.getCustomerFromLocalDB();
    }catch(CustomerNotActiveException){
        ...
        return customerDAO
            .activateCustomerDetails();
    }
}

Handle unchecked exceptions at the web application level

All unchecked exceptions should be handled at the web application level. A web page can be configured in web.xml to be displayed to the end user whenever any unchecked exception occurs in the application.

Wrap third-party exceptions into application-specific exceptions

Whenever an exception originates from other external interfaces (components), it should be wrapped in an application-specific exception and handled accordingly.

Example:


try {
    BeanUtils.copyProperties(areaDTO, areaForm);
} catch (Exception se) {
    throw new CopyPropertiesException(
        "Exception occurred while using 
            copy properties", se);
}

Here, CopyPropertiesException extends java.lang.RuntimeException, as we would just like to log it. We are catching Exception instead of catching the specific checked exceptions that the copyProperties method could throw, since for all of those exceptions we're throwing the same unchecked CopyPropertiesException.

Too Many Exceptions

You may be wondering, if we create an exception for each error message, could we run into in an overflow of exception classes themselves? For instance, if "Order not found" were an error message for OrderNotFoundException, you certainly wouldn't like to have CustomerNotFoundException for an error message "Customer not found" for an obvious reason: the two exceptions represent the same thing, and differ only in the contexts in which they are used. So if we could specify the context while handling the exception, we could certainly reduce these exceptions to just one RecordNotFoundException. Here is an example:

try{
    ...
}catch(BaseAppException ex){
    IExceptionHandler eh =ExceptionHandlerFactory
      .getInstance().create();
    ExceptionDTO exDto = eh.handleException(
        "employee.addEmployee", userId, ex);
}

Here, the employee.addEmployee context will be appended to the error code of a context-sensitive exception, to make a unique resultant error code. For instance, if the error code for RecordNotFoundException is errorcode.recordnotfound, the resultant error code for this context will become errorcode.recordnotfound.employee.addEmployee, which will be a unique error code for this context.

However, there is a caveat: if you are using multiple interfaces in the same client method and they all could throw RecordNotFoundException, it would be really difficult to know for which entity you got this exception. In cases where business interfaces are public and can be used by various external clients, it's recommended to use specific exceptions only and not a generic exception like RecordNotFoundException. Context-specific exceptions are really useful for DB-based recoverable exceptions where exception classes are always the same and the difference comes only in the contexts in which they occur.

Exception Hierarchy for a J2EE Application

As discussed earlier, we need to define a base exception class, say BaseAppException, that contains the default behavior of all application exceptions. We'll just put this base class in throws clause of every method that potentially throws a checked exception. All checked exceptions for an application should be the subclasses of this base class. There are various ways of defining error-handling abstractions. However, these differences should have more to do with business cases and not technical compulsions. The abstraction of error-handling can be divided into the following categories. All of these exceptions classes will derive from BaseAppException.

Checked Exceptions

Unchecked Exceptions

Presentation-Level Exception Handling

The presentation layer is uniquely responsible for deciding the action to be taken for an exception. That decision-making involves identifying the error code based on the exception thrown. Also, we need to know to which screen we should redirect after handling the error.

We need an abstraction for getting the error code based on the type of exception. This should also perform logging when needed. We'll call this abstraction ExceptionHandler. It's a façade based on the "Gang of Four" (GOF) Facade pattern (which the Design Patterns book says is used to "provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.") for the whole exception handling subsystem that handles all of the exceptions derived from BaseAppException. Here is the sample exception handling in a Struts Action method.

 
 
try{ 
    ... 
    DivisionDTO storeDTO = divisionBusinessDelegate 
      .getDivisionByNum(fromDivisionNum); 
}catch(BaseAppException ex){ 
    IExceptionHandler eh = ExceptionHandlerFactory 
      .getInstance().create(); 
    String expContext = "divisionAction.searchDivision"; 
    ExceptionDTO exDto = eh.handleException( 
        expContext , userId, ex); 
    ActionErrors errors = new ActionErrors(); 
        errors.add(ActionErrors.GLOBAL_ERROR 
          ,new ActionError( 
          exDto.getMessageCode())); 
    saveErrors(request, errors); 
    return actionMapping.findForward( 
        "SearchAdjustmentPage"); 
}

If you take a closer look at the exception handling code we just wrote, you might realize that you are writing similar code for each and every Struts method, which is a pain again. The objective is to remove boilerplate code as much as possible. We need to abstract it out again.

The solution is to use the Template Method design pattern (from GOF: "It is used to implement invariant parts of an algorithm once, and to leave it to subclasses to implement parts of the algorithm that can vary."). We need a base class that will contain the algorithm in the form of Template Method. The algorithm will contain the try-catch block for BaseAppException and a call to a dispatchMethod the method implementation (delegated to derived class) as shown below in a Struts based Action:

public abstract class BaseAppDispatchAction 
  extends DispatchAction{
...
protected static ThreadLocal 
  expDisplayDetails = new ThreadLocal();

public ActionForward execute(
  ActionMapping mapping,
  ActionForm form,
  HttpServletRequest request,
  HttpServletResponse response) throws Exception{
    ...
    try{
        String actionMethod = request
           .getParameter(mapping.getParameter());
        finalDestination =dispatchMethod(mapping,
          form, request, response,actionMethod);
    }catch (BaseAppException Ex) {
        ExceptionDisplayDTO expDTO = 
           (ExceptionDisplayDTO)expDisplayDetails
               .get();
        IExceptionHandler expHandler = 
            ExceptionHandlerFactory
                .getInstance().create();
        ExceptionDTO exDto = expHandler
            .handleException(
                expDTO.getContext(), userId, Ex);
        ActionErrors errors = new ActionErrors();
        errors.add(ActionErrors.GLOBAL_ERROR, 
            new ActionError(exDto
                .getMessageCode()));
        saveErrors(request, errors);
        return mapping.findForward(expDTO
            .getActionForwardName());
    } catch(Throwable ex){
           //log the throwable
           //throw ex;
    } finally {
           expDisplayDetails.set(null);
    }

In Struts, the DispatchAction::dispatchMethod method is used to forward the request to the appropriate Action method, named actionMethod.

Let's say you get searchDivision as the actionMethod from an HTTP request: dispatchMethod will dispatch the request to a searchDivision method in the derived Action class of BaseAppDispatchAction. Here, you can see that exception handling is done only in the base class, and the derived class just implements Action methods. It confirms to the Template Method design pattern, where the exception-handling part remains invariant while the actual implementation (the varied part) of dispatchMethod is deferred to the derived class.

The modified Struts Action method mentioned earlier will look something like this after the above changes.

 
... 
String exceptionActionForward = 
    "SearchAdjustmentPage"; 
String exceptionContext = 
    "divisionAction.searchDivision"; 
        
ExceptionDisplayDTO expDTO = 
    new ExceptionDisplayDTO(expActionForward, 
        exceptionContext); 
expDisplayDetails.set(expDTO); 
... 
DivisionDTO divisionDTO =divisionBusinessDelegate 
   .getDivisionByNum(fromDivisionNum); 
...

Wow! Now it looks clean. As exception handling is being done in one centralized place (BaseAppDispatchAction), the scope of manual errors is also minimized.

However, we need to set the exception context and the name of the ActionForward to which the requests will be forwarded if there's an exception. And we are setting this in a ThreadLocal variable, expDisplayDetails.

Hmm. Fine. But why a java.lang.ThreadLocal variable? The expDisplayDetails is a protected data member in the BaseAppDispatchActiion class, and that's why it needs to be thread-safe too. The java.lang.ThreadLocal object comes to the rescue here.

Exception Handler

We talked about an abstraction for handling exceptions in the last section. Here is the contract it should satisfy.

As you might have noticed, the only exception we are catching in the presentation layer is BaseAppException. As all checked exceptions are subclasses of BaseAppException, implicitly, we are catching all of the derived classes of BaseAppException. It's fairly easy to identify the error code based on the name of the class.

//exp is an object of BaseAppException
String className = exp.getClass().getName();

Error codes can be configured in an XML file (named exceptioninfo.xml) based on the name of the exception class. Here is a sample of exception configuration.

<exception name="EmployeeConfirmationException">
    <messagecode>messagecode.laborconfirmation</messagecode>
    <confirmationind>true</confirmationind>
    <loggingtype>nologging</loggingtype>
</exception>

As you can see, we are making it fairly explicit that for this exception, the message code to be used is messagecode.employeeconfirmation. The real message can then be extracted from a ResourceBundle for internationalization purposes. We are also making it very clear that we don't need to perform logging for this type of exception, as it's just a confirmation message and not an application error.

Let's look at an example of a context-sensitive exception:

<exception name="RecordNotFoundException">
    <messagecode>messagecode.recordnotfound</messagecode>
    <confirmationind>false</confirmationind>
    <contextind>true</contextind>
    <loggingtype>error</loggingtype>
</exception>

Here, contextind is true for this exception. The context you passed in handleException method can be used to make a unique error code. For instance, if we passed order.getOrder as a context, the resultant message code will be a concatenation of the exception's message code and the context passed. Hence, we get a unique message code like messagecode.recordnotfound.order.getOrder.

The data coming from exceptioninfo.xml for each exception can be encapsulated into a data transfer object (DTO) named ExceptionInfoDTO. Now we also need a placeholder where we could cache these objects, as we wouldn't want to parse the XML file again and again and create objects each time an exception occurs. This work can be delegated to a class named ExceptionInfoCache, which will cache all ExceptionInfoDTO objects after reading their information from exceptioninfo.xml.

What's this fuss all about, huh? The core of all this is the ExceptionHandler implementation, which will use data encapsulated in ExceptionInfoDTO for getting the message code, creating ExceptionDTO objects, and then logging it based on the type of logging specified in ExceptionInfoDTO for a given exception.

Here is the handleException method of an ExceptionHandler implementation.

public ExceptionDTO handleException(String userId,
      BaseAppException exp) {  
    ExceptionDTO exDTO = new ExceptionDTO();
    ExceptionInfoCache ecache = 
        ExceptionInfoCache.getInstance();
    ExceptionInfo exInfo = ecache
      .getExceptionInfo(
        ExceptionHelper.getClassName(exp));
    String loggingType = null;
    if (exInfo != null) {
        loggingType = exInfo.getLoggingType();
        exDTO.setConfirmation(exInfo
            .isConfirmation());
        exDTO.setMessageCode(exInfo
            .getMessageCode());
    }

    FileLogger logger = new FileLoggerFactory()
        .create();
    logger.logException(exp, loggingType);

Depending upon business requirements, there can be multiple implementations of the ExceptionHandler interface. Deciding which implementation to use can be delegated to a Factory, specifically a ExceptionHandlerFactory class.

Conclusion

Without a comprehensive exception-handing strategy, an ad hoc collection of exception-handling blocks can lead to non-standard error handling and non-maintainable code. Using the above approach, exception handling can be streamlined in a J2EE application.

Resources

ShriKant Vashishtha currently works as a solution architect for Tata Consultancy Services Limited (TCS), India.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.