Jim Majure's Blog

Thursday Sep 13, 2007

Exception Handling in Java

As a consultant working on J2EE applications, it never ceases to amaze me how poorly exception handling is done in mission critical applications. It seems that often the first thing that I do when I begin working on an application in a new engagement is to review and rework the exception handling code.

In this blog entry I will discuss some common problems and present some strategies for effective exception handling. None of the information contained here is exactly new. Numerous books provide guidelines for exception handling and some frameworks have made changes in their use of exceptions so that they fit more nicely into an application-wide exception handling strategy. I'm mostly writing this so that I have a single, accessible spot to which to point interested people.

Common Problems

Let's look at the most common poor exception handling practice. The following code fragment is quite common in Java applications. I call this the "catch and gobble".

method m() {
    ...  // 1
    try {
        ... // 2
        someObject.someMethod(); // code that throws a checked exception...
        ... // 3
    } catch (SomeException e) {
        logger.error("An error occurred.", e);
    }
    ... // 4
}

A developer is coding along and runs into an API that throws a checked exception. When this happens, the compiler complains that the exception is not being handled correctly. The developer must either catch and "handle" the exception, or declare it to be thrown out of the method. Often the exception in question is quite general (i.e., could have been caused by a large number of specific conditions) and quite difficult to truly handle in the current method.

The easiest and most expedient approach is to "catch and gobble" the exception. The above code does the trick. The exception is taken care of and the developer can continue to work on the code path of interest.

This method is executing at the top of a call stack, which may look like this for a typical web application.

** a call stack to be inserted **

Here is a synopsis of the relevant parts of the stack:

  • The servlet that accepts the HTTP request
  • The controller that manages session state and delegates to business logic
  • The interceptor(s) that begin a transaction boundary
  • Our method, which is implementing some business logic
  • In the rare case that this exception is actually thrown, the code that corresponds to line 3 is not executed, while line 4 is executed (as well as the remainder of the call stack). This can lead to a range of side effects, depending on what this code does. Here are a few possiblities:

    1. The current request is completed without throwing any other exceptions and a response is presented back to the user. The user is unaware that an exception occurred, even though the transaction was not completed successfully (because not all of the code executed).
    2. The missing code causes another exception to occur after the method finishes executing. This exception is reported to the user, but the error message (and the corresponding logged exception) in no way indicate the true root cause of the problem.
    3. Because this code is executing within transaction boundaries, it is possible that bad data is inserted into the database without ever notifying the user that a problem has been encountered. This bad data might cause problems in subsequent transactions or even subsequent portions of the business process being executed by another user. This might happen later the same day, the next day, or possibly months later.

    The bottom line is that we don't really know what will happen in this situation, but this type of coding can introduce problems that are very difficult to find and can be enormously costly if the persistent data store is corrupted.

    There are exceptions, and there are exceptions...

    It's helpful, at this point, to be more specific about the types of exceptions that we are talking about. I find it useful to think about 3 types of exceptions:

    1. Coding errors
    2. System failures
    3. Business logic exceptions

    Coding errors are mistakes made by programmers that cause an exception to be thrown at runtime. For example, if a third-party API expects a non-null parameter, but the programmer passes a null value. This generates a NullPointerException at runtime (or, if we're lucky, a IllegalArgumentException).

    System failures occur when some part of the computing infrastructure fails. Examples include the network going down, the db server crashing, etc. These situations generate many different types of exceptions at runtime.

    Business logic exceptions represent business rules that are violated during processing. An example might be creating two instances of a Customer object and give them both the same SSN. This might be indicated, for example, as a db constraint violation.

    Business logic exceptions, by their nature, require the developer to write specific exception handling logic in order to notify the user of the problem and provide enough information to resolve it. When I'm doing development, I should know about the various business logic exceptions that I might encounter and also know what to do to in response.

    Coding errors and system failures - however, are completely unpredictable. (If you could predict a programming error, wouldn't you fix it? We never expect systems to fail, but it does happen.) These are the types of problems that lead to the poor exception handling practices illustrated above. In the following discussion, we will focus on these two types of exceptions and see if we can identify strategies that handle them effectively without placing an undue burden on the developer.

    Checked vs Unchecked Exceptions

    Many of the exception handling problems are due to the fact that Java supports both checked and unchecked exceptions. When checked exceptions are used, the Java compiler forces the developer to explicitly handle the exception with a try/catch(/finally) block, or declare it to be thrown out of the method. When APIs throw checked exceptions for coding errors or system failures, the most common response by developers is to do the catch and gobble.

    An unchecked exception, on the other hand, does not have to be handled by the code. An unchecked exception with be thrown out of the method and continue to pop methods off the stack until it is explicitly trapped in the code. Unchecked exceptions include all exception classes that extend (directly or indirectly) from RuntimeException.

    If unchecked exceptions were used by all API's that throw exceptions for coding errors or system failures, there would be a lot less bad exception handling. A number of frameworks have changed their exception handling strategies due to this problem. Both Spring and Hibernate now throw unckecked exceptions for most framework related problems. There are, however, still lots and lots of APIs around that throw checked exceptions, so we need a way to deal with it.

    Goals for Exception Handling

    Our exception handling strategy should accomplish the following goals:

    1. Stop the current execution path to ensure that the exception doesn't cause additional problems in subsequent execution
    2. Rollback any db transactions that have been begun, but not completed
    3. Insure consistency of the conversational state of the application given that the current request was not completed
    4. Pass the exception to a global exception handler that handles the exception by logging the exception to the system log file, sending emails, firing JMX notifications, and/or any other appropriate actions
    5. Provide immediate notification to the user that an exception occurred and that the current request did not complete
    6. Make handling exceptions easy for the developer

    Handling Exceptions 101

    The most straightforward way to deal with a checked exception that cannot be handled is the following:

    method m() {
        ...  // 1
        try {
            ... // 2
            someObject.someMethod(); // code that throws a checked exception...
            ... // 3
        } catch (SomeException e) {
            throw new RuntimeException("some message", e);
        }
        ... // 4
    }
    

    This code simply wraps the checked exception in an unchecked exception and re-throws it. This unchecked exception will travel back down the call stack until it is explicitly handled. Let's see how this stacks up to our goals:

    1. The current execution path is stopped, no subsequent code in this method or down the call stack is executed
    2. When the exception reaches the transactional interceptor, any db transactions in progress are rolled back. After the transactions are rolled back, the exception is re-thrown out of the interceptor so that it can be handled by the application.
    3. To ensure the consistence of conversational state, the code that manages such state must be developed to take potential exceptions into account. We'll talk more about this in a later section.
    4. A global exception handler must be registered at the appropriate location of the call stack. This typically in the UI application framework or the Servlet API. The framework will trap any exceptions that reach it, and pass them to the registered exception handler. We'll talk more about the exception handler in a later section.
    5. The last step is to notify the user of the error. Again, most UI frameworks tie this capability in with the exception handler.
    6. This approach clearly relieves the developer of much responsibility. If you can't deal with an exception, then simply wrap it in an unchecked exception and trust that somebody registered an exception handler in the right location.

    If you do nothing but this in your applications, your exception handling will have improved tremendously. You are guaranteed to know about ALL exceptions that are thrown at runtime during the execution of your system. It's far better to know about exceptions and then develop responses in specific situations, than to bury the exceptions and hope it doesn't lead to negative consequences.

    This is the starting point for better exception handling. In the following sections, I will put a finer point on some details.

    Global Exception Handlers

    A key element to a good exception handling strategy is a global exception handler. The global exception handler has two primary responsibilities:

    1. to present a message to the user (if there is one) indicating that an exception has occurred and that the request was not completely satisfied
    2. to notify operations staff of the exception through logging, emails, JMX notifications, SNMP traps, etc.

    Presenting an error page is important because it lets the user know that an error occurred, which must be done. It also ensures that the exact technical nature of the error is not presented back to the user. It's not uncommon, for example, to see a stack trace or low-level exception message displayed in a web page when an error occurs. This type of information provides important knowledge to hackers attempting to compromise an application.

    Most UI frameworks provide a mechanism to declaratively define pages to be displayed when exceptions are caught. The servlet specification even allows declarative exception handling using the <errorpage> tag in the web.xml file.

    In addition to showing an appropriate error page, the exception must be communicated to operational personnel so that they can take steps to correct the problem if necessary. This typically requires the creation of an exception handling class that contains the code to conduct the notifications. Start with an interface like this:

    
    public interface ExceptionHandler {
        void handleException(Throwable t);
    }
    
    

    The implementation of this class contains the notification code. Some possible behaviors include:

    1. Logging exceptions to the system log file
    2. Sending an email to an email group
    3. Firing SNMP traps

    This class can contain any logic necessary to effectively handle exceptions. The key is that there is a single code path that is responsible for handling all exceptions in the application.

    After the exception handler class is written with the appropriate logic, it must be installed in the application. Most UI frameworks have a mechanism to register exception handling classes so that exceptions are delegated appropriately. If not, it is usually possible to introduce a superclass in the right location that traps exceptions and passes them to the exception handler.

    Example

    Let's look at a example of an exception handler for a Spring MVC web application. Spring MVC has the HandlerExceptionResolver interface to resolve exceptions encountered in request handlers to views. The SimpleMappingExceptionResolver is a simple implementation that maps exception class names to view names. This class can be extended to delegate to our global exception handler prior to redirecting to an appropriate error view.

    class MyHandlerExceptionResolver extends SimpleMappingExceptionHandler {
        ExceptionHandler myHandler;
    
        ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
            myHandler.handleException(ex);
            return super.resolveException(request,response, handler, ex);
        }
    }
    
    

    By installing this exception resolver into a Spring MVC application, we've done two things: 1) directed all unhandled exceptions to our exception handling class; and 2) redirected the UI to an appropriate view.

    Managing Conversational State

    More to come...

    Adding Information to Exceptions

    More to come...

    Configuration Problems

    Coding Errors

    More to come...

    Handling Exceptions in Batch Processing

    More to come...

    Comments:

    Post a Comment:
    • HTML Syntax: NOT allowed

    Calendar

    Feeds

    Search

    Links

    Navigation

    Referrers