Exception Handling


Introduction

Exception handling in DeltaSpike is based around the CDI eventing model. While the implementation of exception handlers may not be the same as a CDI event, and the programming model is not exactly the same as specifying a CDI event / observer, the concepts are very similar. DeltaSpike makes use of events for many of its features. Eventing is actually the only way to start using DeltaSpike's exception handling.

This event is fired either by the application or a DeltaSpike exception handling integration. DeltaSpike then hands the exception off to a chain of registered handlers, which deal with the exception appropriately. The use of CDI events to connect exceptions to handlers makes this strategy of exception handling non-invasive and minimally coupled to the exception handling infrastructure.

The exception handling process remains mostly transparent to the developer. In most cases, you register an exception handler simply by annotating a handler method. Alternatively, you can handle an exception programmatically, just as you would observe an event in CDI.

Exception Handling - Usage

Eventing into the exception handling framework

The entire exception handling process starts with an event. This helps keep your application minimally coupled to DeltaSpike, but also allows for further extension. Exception handling in DeltaSpike is all about letting you take care of exceptions the way that makes the most sense for your application. Events provide this delicate balance. Firing the event is the main way of starting the exception handling proccess.

Manually firing an event to use DeltaSpike's exception handling is primarily used in your own try/catch blocks. It's very painless and also easy. Let's examine a sample that might exist inside of a simple business logic lookup into an inventory database:

public class InventoryActions {
  @PersistenceContext private EntityManager em;
  @Inject private Event<ExceptionToCatchEvent> catchEvent;

  public Integer queryForItem(Item item) {
    try {
      Query q = em.createQuery("SELECT i from Item i where i.id = :id");
      q.setParameter("id", item.getId());
      return q.getSingleResult();
   } catch (PersistenceException e) {
     catchEvent.fire(new ExceptionToCatchEvent(e));
   }
  }
}

The {{Event}} of generic type {{ExceptionToCatchEvent}} is injected into your class for use later within a try/catch block.

The event is fired with a new instance of {{ExceptionToCatchEvent}} constructed with the exception to be handled.

Exception handlers

As an application developer (i.e., an end user of DeltaSpike's exception handling), you'll be focused on writing exception handlers. An exception handler is a method on a CDI bean that is invoked to handle a specific type of exception. Within that method, you can implement any logic necessary to handle or respond to the exception.

If there are no exception handlers for an exception, the exception is rethrown.

Given that exception handler beans are CDI beans, they can make use of dependency injection, be scoped, have interceptors or decorators and any other functionality available to CDI beans.

Exception handler methods are designed to follow the syntax and semantics of CDI observers, with some special purpose exceptions explained in this guide. The advantage of this design is that exception handlers will be immediately familiar to you if you are studying or well-versed in CDI.

In this and subsequent sections, you'll learn how to define an exception handler, explore how and when it gets invoked, modify an exception and a stack trace, and even extend exception handling further through events that are fired during the handling workflow. We'll begin by covering the two annotations that are used to declare an exception handler, {{@ExceptionHandler}} and {{@Handles}}, and {{@BeforeHandles}} to create a callback before the handler is called.

Exception handler annotations

Exception handlers are contained within exception handler beans, which are CDI beans annotated with {{@ExceptionHandler}}. Exception handlers are methods which have a parameter which is an instance of {{ExceptionEvent}} annotated with the {{@Handles}} annotation.

{{@ExceptionHandler}}

The {{@ExceptionHandler}} annotation is simply a marker annotation that instructs the DeltaSpike exception handling CDI extension to scan the bean for handler methods.

Let's designate a CDI bean as an exception handler by annotating it with {{@ExceptionHandler}}.

@ExceptionHandler
public class MyHandlers {}

That's all there is to it. Now we can begin defining exception handling methods on this bean.

{{@Handles}} and {{@BeforeHandles}}

{{@Handles}} is a method parameter annotation that designates a method as an exception handler. Exception handler methods are registered on beans annotated with {{@ExceptionHandler}}. DeltaSpike will discover all such methods at deployment time.

Let's look at an example. The following method is invoked for every exception that DeltaSpike processes and prints the exception message to stdout. ({{Throwable}} is the base exception type in Java and thus represents all exceptions).

@ExceptionHandler
public class MyHandlers
{
   void printExceptions(@Handles ExceptionEvent<Throwable> evt)
   {
      System.out.println("Something bad happened: " +
            evt.getException().getMessage());
      evt.handleAndContinue();
   }
}

The {{@Handles}} annotation on the first parameter designates this method as an exception handler (though it is not required to be the first parameter). This parameter must be of type {{ExceptionEvent}}, otherwise it's detected as a definition error. The type parameter designates which exception the method should handle. This method is notified of all exceptions (requested by the base exception type {{Throwable}}).

The {{ExceptionEvent}} instance provides access to information about the exception and can be used to control exception handling flow. In this case, it's used to read the current exception being handled in the exception chain, as returned by {{getException()}}.

This handler does not modify the invocation of subsequent handlers, as designated by invoking {{handleAndContinue()}} on {{ExceptionEvent}}. As this is the default behavior, this line could be omitted.

The {{@Handles}} annotation must be placed on a parameter of the method, which must be of type {{ExceptionEvent}}. Handler methods are similar to CDI observers and, as such, follow the same principles and guidelines as observers (such as invocation, injection of parameters, qualifiers, etc) with the following exceptions:

  • a parameter of a handler method must be a {{ExceptionEvent}}
  • handlers are ordered before they are invoked (invocation order of observers is non-deterministic)
  • any handler can prevent subsequent handlers from being invoked

In addition to designating a method as exception handler, the {{@Handles}} annotation specifies an {{ordinal}} about when the method should be invoked relative to other handler methods of the same type. Handlers with higher ordinal are invoked before handlers with a lower ordinal that handle the same exception type. The default ordinal (if not specified) is 0.

The {{@BeforeHandles}} designates a method as a callback to happen before handlers are called.

Let's take a look at more sophisticated example that uses all the features of handlers to log all exceptions.

@ExceptionHandler
public class MyHandlers
{
   void logExceptions(@BeforeHandles @WebRequest ExceptionEvent<Throwable> evt,
         Logger log)
   {
      log.warn("Something bad happened: " + evt.getException().getMessage());
   }

   void logExceptions(@Handles @WebRequest ExceptionEvent<Throwable> evt,
         Logger log)
   {
      // possibly send a HTTP Error code
   }
}

This handler has a default ordinal of 0 (the default value of the ordinal attribute on {{@Handles}}).

This handler is qualified with {{@WebRequest}}. When DeltaSpike calculates the handler chain, it filters handlers based on the exception type and qualifiers. This handler will only be invoked for exceptions passed to DeltaSpike that carry the {{@WebRequest}} qualifier. We'll assume this qualifier distinguishes a web page request from a REST request.

Any additional parameters of a handler method are treated as injection points. These parameters are injected into the handler when it is invoked by DeltaSpike. In this case, we are injecting a {{Logger}} bean that must be defined within the application (or by an extension).

A handler is guaranteed to only be invoked once per exception (automatically muted), unless it re-enables itself by invoking the {{unmute()}} method on the {{ExceptionEvent}} instance.

Handlers must not throw checked exceptions, and should avoid throwing unchecked exceptions. Should a handler throw an unchecked exception it will propagate up the stack and all handling done via DeltaSpike will cease. Any exception that was being handled will be lost.

Exception Chain Processing

When an exception is thrown, chances are it's nested (wrapped) inside other exceptions. (If you've ever examined a server log, you'll appreciate this fact). The collection of exceptions in its entirety is termed an exception chain.

The outermost exception of an exception chain (e.g., EJBException, ServletException, etc) is probably of little use to exception handlers. That's why DeltaSpike doesn't simply pass the exception chain directly to the exception handlers. Instead, it intelligently unwraps the chain and treats the root exception cause as the primary exception.

The first exception handlers to be invoked by DeltaSpike are those that match the type of root cause. Thus, instead of seeing a vague {{EJBException}}, your handlers will instead see an meaningful exception such as {{ConstraintViolationException}}. This feature, alone, makes DeltaSpike's exception handling a worthwhile tool.

DeltaSpike continues to work through the exception chain, notifying handlers of each exception in the stack, until a handler flags the exception as handled or the whole exception chain has been iterated. Once an exception is marked as handled, DeltaSpike stops processing the exception chain. If a handler instructs DeltaSpike to rethrow the exception (by invoking {{ExceptionEvent#throwOriginal()}}, DeltaSpike will rethrow the exception outside the DeltaSpike exception handling infrastructure. Otherwise, it simply returns flow control to the caller.

Consider a exception chain containing the following nested causes (from outer cause to root cause):

  • EJBException
  • PersistenceException
  • SQLGrammarException

DeltaSpike will unwrap this exception and notify handlers in the following order:

  • SQLGrammarException
  • PersistenceException
  • EJBException

If there's a handler for {{PersistenceException}}, it will likely prevent the handlers for {{EJBException}} from being invoked, which is a good thing since what useful information can really be obtained from {{EJBException}}?

Handler ordinal

When DeltaSpike finds more than one handler for the same exception type, it orders the handlers by ordinal. Handlers with higher ordinal are executed before handlers with a lower ordinal. If DeltaSpike detects two handlers for the same type with the same ordinal, the order is non-deterministic.

Let's define two handlers with different ordinals:

void handleIOExceptionFirst(@Handles(ordinal = 100) ExceptionEvent<IOException> evt)
{
   System.out.println("Invoked first");
}

void handleIOExceptionSecond(@Handles ExceptionEvent<IOException> evt)
{
   System.out.println("Invoked second");
}

The first method is invoked first since it has a higher ordinal (100) than the second method, which has the default ordinal (0).

To summarize, here's how DeltaSpike determines the order of handlers to invoke (until a handler marks exception as handled):

# Unwrap exception stack
# Begin processing root cause
# Invoke any callback methods annotated with @BeforeHandles for the closest type to the exception
# Find handler for the closest type to the exception
# If multiple handlers for same type, invoke handlers with higher ordinal first
# Continue above steps for each exception in stack

APIs for exception information and flow control

There are two APIs provided by DeltaSpike that should be familiar to application developers:

  • {{ExceptionEvent}}
  • {{ExceptionStackEvent}}

ExceptionEvent

In addition to providing information about the exception being handled, the {{ExceptionEvent}} object contains methods to control the exception handling process, such as rethrowing the exception, aborting the handler chain or unmuting the current handler. Five methods exist on the {{ExceptionEvent}} object to give flow control to the handler

  • {{abort()}} - terminate all handling immediately after this handler, does not mark the exception as handled, does not re-throw the exception.
  • {{throwOriginal()}} - continues through all handlers, but once all handlers have been called (assuming another handler does not call abort() or handled()) the initial exception passed to DeltaSpike is rethrown. Does not mark the exception as handled.
  • {{handled()}} - marks the exception as handled and terminates further handling.
  • {{handleAndContinue()}} - default. Marks the exception as handled and proceeds with the rest of the handlers.
  • {{skipCause()}} - marks the exception as handled, but proceeds to the next cause in the cause container, without calling other handlers for the current cause.
  • {{rethrow(Throwable)}} - Throw a new exception after this handler is invoked

Once a handler is invoked it is muted, meaning it will not be run again for that exception chain, unless it's explicitly marked as unmuted via the {{unmute()}} method on {{ExceptionEvent}}.