Chapter 11. Exception handling

11.1. Why not just use try and catch?

Most exceptions that are thrown are unexpected: we don't expect them to happen (especially during production) such as:

  • NullPointerException: Didn't I double checked all my source code to avoid NPE's?

  • CvsParserException: Why did the user pick a html file when I asked him for a CVS file?

  • IDidNotKnowThisExistedRuntimeException: What the ...?

And if you except some of them, you ussually can't really fix the problem, just deal with it:

  • Log the exception.

  • Notify the user that whatever he tried didn't work, preferably with an not-technical, exception-specific explanation.

  • Either shutdown the application or allow the user to continue (and try again).

You could use try-catch during every user action:

protected boolean onFinish() {
    try {
        form.getFormModel().commit();
        // ...
        getApplicationContext().publishEvent(new LifecycleApplicationEvent(eventType, getEditingContact()));
        return true;
    } catch (Throwable throwable) {
        handleException(throwable);
    }
}

But this is tedious and error prone:

  • It's easy to forget to try catch some code, which makes the exception escape to the top layer exception handler.

  • You could unwillingly eat the exception, not logging it:

    • If you handle an exception, but forget to log it and/or show it to the user.

    • If you throw an exception in the catch or finally part, only the last exception bubbles up, effectively hiding the real exception.

    In production, this leads to discussions where the user is sure he pushed the button (which he did in this case) and the programmer is sure the user didn't because the system didn't report anything and nothing has changed. If you notice that while you are fixing a issue, an exception is eaten (making it hard to identify the original issue), create a new issue because exceptions are eaten and fix that first.

  • You are in danger to handle the same exception on 2 different layers, effectively logging it or notifing the user twice.

  • In some layers or parts of the application, it might not be clear if you need to notify the user (and which user) through a swing dialog or JSP or webservice response.

Spring-richclient's exception handling system uses the top layer exception handling. It expects that all other layers let the exception bubble up.

11.2. Registering an exception handler

In the LifecycleAdvisor you can register a RegisterableExceptionHandler:

<bean id="petclinicLifecycleAdvisor" class="....PetClinicLifecycleAdvisor">
  <!-- ... -->
  <property name="registerableExceptionHandler" ref="exceptionHandler" />
</bean>

<bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.SilentExceptionHandler"/>

When an exception handler is registered, it will ussually register itself as the UncaughtExceptionHandler on the threads. However, the event thread catches a throwable thrown in any event, to prevent getting killed, so it also registers itself to the event thread specifically (regretably this is currently sun JRE specific).

TODO: What happens to exceptions thrown in a pooled SwingWorker or Future as they don't kill the thread, nor do they occur in the event thread? They are probably rethrown on the event thread?

11.3. Build-in exception handlers

Most of these exception handlers are part of the spring-richclient-tiger module.

11.3.1. SilentExceptionHandler

Logs a throwable but does not notify the user in any way. Normally it is a bad practice not to notify the user if something goes wrong.

You can set a log level on it:

<bean class="org.springframework.richclient.exceptionhandling.SilentExceptionHandler">
  <property name="logLevel" value="WARN"/>
  <!-- ... -->
</bean>

This means that any exception handled by this exception handler will be logged at the warn level.

11.3.2. MessagesDialogExceptionHandler

Shows the exception in a dialog to the user (as well as logging it). You can set a log level and the icon of the dialog depends on that log level. The shown dialog has a caption (= dialog title) and description (= dialog content), which are fetched from the i18n messages files. There are 2 ways to resolve those messages: static or dynamic (default).

You can statically set the title and description by setting the messagesKey property. However, it's a lot more powerfull to use the default dynamic behaviour based on the class of the exception. For example if a NumberFormatException is thrown, it will first look for these i18n keys:

java.lang.NumberFormatException.caption=Not a number
java.lang.NumberFormatException.description=\
  You did not enter a a valid number.\n\
  \n\
  Please enter a valid number.

If these messages keys don't exist, it will fall back to the parent class of NumberFormatException, which is IllegalArgumentException:

java.lang.IllegalArgumentException.caption=...
java.lang.IllegalArgumentException.description=...

It will continue to fall back up the chain, untill it reaches Throwable. This allows you to direct all unexpected exceptions (for example IDidNotKnowThisExistedRuntimeException) to a MessagesDialogExceptionHandler that logs them as an error and shows a generic message. You can even use {0} in your i18n message to show the exception.getMessage() in the description:

# Double quotes(") need to be escaped (\"), single quotes (') always seem to break the replacing of {0}.
java.lang.RuntimeException.caption = Unexpected general bug
java.lang.RuntimeException.description = \
  The application experienced an unexpected bug,\n\
  due to a programming error.\n\
  \n\
  The application is possibly in an inconsistent state.\n\
  It is recommended to reboot the application.\n\
  \n\
  The exact bug is:\n\
  {0}\n\
  \n\
  Please report this bug.

java.lang.Error.caption = Unexpected serious system failure
java.lang.Error.description = \
  A serious system failure occured.\n\
  \n\
  The application is possibly in an inconsistent state.\n\
  Reboot the application.\n\
  \n\
  The exact bug is:\n\
  {0}\n\
  \n\
  Please report this bug.

Note that, although this dynamic system is pretty powerfull and avoids a lot of boilerplate, it's ussually not a replacement for DelegatingExceptionHandler, because it doesn't allow you to assign different log levels, etc.

You can set a shutdown policy on a dialog exception handler:

<bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler">
  <property name="shutdownPolicy" value="ASK" />
  <!-- ... -->
</bean>

This allows you to optionally enforce or propose a System.exit(1).

11.3.3. HibernateValidatorDialogExceptionHandler

A special exception handler which can only handle an InvalidStateException thrown by Hibernate validator. It shows the failed validations to a user in a list in a dialog. In most cases it's inferiour to the HibernateRulesValidator which validates before the user presses the commit button. But because the latter forces you to hand code @AssertTrue's and it could be working on stale client-side data, it's actually a very nice backup to also configure this exception handler:

<!-- Inside a delage list of DelegatingExceptionHandler -->
<bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate">
  <property name="throwableClass">
    <value type="java.lang.Class">org.hibernate.validator.InvalidStateException</value>
  </property>
  <property name="exceptionHandler">
    <bean class="org.springframework.richclient.exceptionhandling.HibernateValidatorDialogExceptionHandler">
      <property name="logLevel" value="INFO" />
      <property name="shutdownPolicy" value="NONE" />
    </bean>
  </property>
</bean>

11.3.4. Custom exception handler

You can also extend AbstractLoggingExceptionHandler and implement this method:

public void notifyUserAboutException(Thread thread, Throwable throwable) {
    // ...
}

11.4. Picking the right exception handler for the right exception

There are bunch of build-in exception handlers, but ussually there isn't one exception handler that fits alls exceptions. A DelegatingExceptionHandler allows you to delegate an exception to the right exception handler. It accepts a delegate list and traverses that list in order. The first delegate that can handle the exception, has to handle the exception and the rest of the delegate list is ignored.

For example, here we configure authentication and authorization exceptions to a WARN MessagesDialogExceptionHandler, hibernate validator exceptions to an INFO HibernateValidatorDialogExceptionHandler and the rest to an ERROR MessagesDialogExceptionHandler.

  <bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.delegation.DelegatingExceptionHandler">
    <property name="delegateList">
      <list>
        <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate">
          <property name="throwableClassList">
            <list>
              <value type="java.lang.Class">org.acegisecurity.AuthenticationException</value>
              <value type="java.lang.Class">org.acegisecurity.AccessDeniedException</value>
            </list>
          </property>
          <property name="exceptionHandler">
            <bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler">
              <property name="logLevel" value="WARN" />
              <property name="shutdownPolicy" value="NONE" />
            </bean>
          </property>
        </bean>
        <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate">
          <property name="throwableClass">
            <value type="java.lang.Class">org.hibernate.validator.InvalidStateException</value>
          </property>
          <property name="exceptionHandler">
            <bean
              class="org.springframework.richclient.exceptionhandling.HibernateValidatorDialogExceptionHandler">
              <property name="logLevel" value="INFO" />
              <property name="shutdownPolicy" value="NONE" />
            </bean>
          </property>
        </bean>
        <!-- The order is important: if Throwable would be first then the others would be ignored -->
        <bean
          class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate">
          <property name="throwableClass" value="java.lang.Throwable" />
          <property name="exceptionHandler">
            <bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler">
              <property name="logLevel" value="ERROR" />
              <property name="shutdownPolicy" value="ASK" />
            </bean>
          </property>
        </bean>
      </list>
    </property>
  </bean>

11.4.1. SimpleExceptionHandlerDelegate

Processes the exception if it is an instance of throwableClass or the throwableClassList.

11.4.2. ChainInspectingExceptionHandlerDelegate

In most cases this class is overkill and SimpleExceptionHandlerDelegate with a purger will suffice. However if those don't suffice, read the javadoc of this class.

11.4.3. ExceptionPurger

An exception purger allows you to cream off wrapper exceptions. This allows you to handle a chained exception in the chain of the uncaught exception, instead of the uncaught exception itself. Almost all exception handlers and delegate's support the use of a purger. DefaultExceptionPurger supports 2 ways to identify the depth to cream off: include or exclude based.

A chained exception of the type in the includeThrowableClassList is stripped from all it's wrapper exceptions and handled by the exception handler or evaluated by the delegate. For example, we want to handle every MySQLIntegrityConstraintViolationException even if it's wrapped:

<bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate">
   <property name="exceptionPurger">
     <bean class="org.springframework.richclient.exceptionhandling.delegation.DefaultExceptionPurger">
        <property name="includeThrowableClassList">
          <list>
            <value type="java.lang.Class">com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException</value>
          </list>
        </property>
     </bean>
   </property>
   <property name="throwableClassList">
     <list>
       <value type="java.lang.Class">com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException</value>
     </list>
   </property>
   <property name="exceptionHandler">
     <!-- ... -->
   </property>
</bean>

A chained exception of the type in the excludeThrowableClassList is stripped together with all it's wrapper exceptions and it's cause is handled by the exception handler or evaluated by the delegate. For example the server wraps all exceptions in an annoying, useless WrappingServiceCallException and we want to get rid of it:

<bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.delegation.DelegatingExceptionHandler">
   <property name="exceptionPurger">
     <bean class="org.springframework.richclient.exceptionhandling.delegation.DefaultExceptionPurger">
        <property name="excludeThrowableClassList">
          <list>
            <value type="java.lang.Class">foo.bar.WrappingServiceCallException</value>
          </list>
        </property>
     </bean>
   </property>
  <property name="delegateList">
    <!-- ... -->
  </property>
</bean>