Exceptions are a fact of life. As developers, we need to be prepared to deal with them in the most graceful manner possible. Seam Catch provides a simple, yet robust foundation for modules and/or applications to establish a customized exception handling process. By employing a delegation model, Catch allows exceptions to be addressed in a centralized, extensible and uniform manner.
Catch is first notified of an exception to be handled via a CDI event. This event is fired either by the application or a Catch integration. Catch 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 Catch's infrastructure.
The exception handling process remains mostly transparent to the developer. In some 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.
In this guide, we'll explore the various options you have for handling exceptions using Catch, as well as how framework authors can offer Catch integration.
To use the Seam Catch module, you need to add the Seam Catch API to your project as a compile-time dependency. At runtime, you'll also need the Seam Catch implementation, which you either specify explicitly or through a transitive dependency of another module that depends on it (as part of exposing its own Catch integration).
First, check your application's library dependencies to see whether Seam Catch is already being included by another module (such as Seam Servlet). If not, you'll need to setup the dependencies as described below.
If you are using Maven as your build tool, you can add the following single dependency to your pom.xml file to include Seam Catch:
<dependency>
<groupId>org.jboss.seam.catch</groupId>
<artifactId>seam-catch</artifactId>
<version>${seam.catch.version}</version>
</dependency>
Substitute the expression ${seam.catch.version} with the most recent or appropriate version of Seam Catch. Alternatively, you can create a Maven user-defined property to satisfy this substitution so you can centrally manage the version.
Alternatively, you can use the API at compile time and only include the implementation at runtime. This protects you from inadvertantly depending on an implementation class.
<dependency>
<groupId>org.jboss.seam.catch</groupId>
<artifactId>seam-catch-api</artifactId>
<version>${seam.catch.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jboss.seam.catch</groupId>
<artifactId>seam-catch-impl</artifactId>
<version>${seam.catch.version}</version>
<scope>runtime</scope>
</dependency>
Now you're ready to start catching exceptions!
As an application developer (i.e., an end user of Catch), 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.
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 chapter, you'll learn how to define an exception handler and explore how and when it gets invoked.
We'll
begin by covering the two annotations that are used to declare an exception handler,
@HandlesExceptions
and @Handles
.
Exception handlers are contained within exception handler beans, which are CDI beans annotated with
@HandlesExceptions
. Exception handlers are methods which have a parameter which is an
instance of CaughtException<T extends Throwable>
annotated with the
@Handles
annotation.
The
@HandlesException
annotation is simply a marker annotation that instructs the Seam
Catch CDI extension to scan the bean for handler methods.
Let's designate a CDI bean as an exception handler by annotating it with
@HandlesException
.
@HandlesExceptions
public class MyHandlers {}
That's all there is to it. Now we can begin defining exception handling methods on this bean.
@HandlesExceptions
annotation may be deprecated in favor of annotation indexing
done by Seam Solder.
@Handles
is a method parameter annotation that designates a method as an exception
handler. Exception handler methods are registered on beans annotated with
@HandlesExceptions
. Catch will discover all such methods at deployment time.
Let's look at an example. The following method is invoked for every exception that Catch processes and
prints the exception message to stout. (Throwable
is the base exception type in Java and
thus represents all exceptions).
@HandlesExceptions
public class MyHandlers
{void printExceptions(@Handles CaughtException<Throwable> evt)
{
System.out.println("Something bad happened: " +evt.getException().getMessage());
evt.markHandled();
}
}
The | |
The | |
The | |
This handler does not modify the invocation of subsequent handlers, as designated by invoking
markHandled() on CaughtException . 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 CaughtException<T extends Throwable>
. 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:
CaughtException
In addition to designating a method as exception handler, the @Handles
annotation specifies two pieces of information about when the method should be invoked relative to other
handler methods:
TraversalMode.DEPTH_FIRST
.
Let's take a look at more sophisticated example that uses all the features of handlers to log all exceptions.
@HandlesExceptions
public class MyHandlers
{void logExceptions(@Handles(during = TraversalMode.BREADTH_FIRST)
@WebRequest CaughtException<Throwable> evt,
Logger log)
{
log.warn("Something bad happened: " + evt.getException().getMessage());
}
}
The
| |
This handler has a default precedence of 0 (the default value of the precedence attribute on
| |
This handler is qualified with | |
Any additional parameters of a handler method are treated as injection points. These parameters are
injected into the handler when it is invoked by Catch. In this case, we are injecting a
|
A handler is guaranteed to only be invoked once per exception (automatically muted), unless it reenables
itself by invoking the
unmute()
method on the
CaughtException
instance.
Handlers must not throw checked exceptions, and should avoid throwing unchecked exceptions. Should a handler throw an unchecked exception it will propegate up the stack and all handling done via Catch will cease. Any exception that was being handled will be lost.
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 stack trace.
The outermost exception of an exception stack trace (e.g., EJBException, ServletException, etc) is probably of little use to exception handlers. That's why Catch doesn't simply pass the exception stack trace directly to the exception handlers. Instead, it intelligently unwraps the stack trace and treats the root exception cause as the primary exception.
The first exception handlers to be invoked by Catch 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 Catch a worthwhile
tool.
Catch continues to work through the exception stack trace, notifying handlers of each exception in the stack,
until a handler flags the exception as handled. Once an exception is marked as handled, Catch stops processing
the exception. If a handler instructed Catch to rethrow the exception (by invoking
CaughtException#rethrow()
, Catch will rethrow the exception outside the Catch
infrastructure. Otherwise, it simply returns flow control to the caller.
Consider a stack trace containing the following nested causes (from outer cause to root cause):
Catch will unwrap this exception and notify handlers in the following order:
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
?
While processing one of the causes in the exception stack trace, Catch has a specific order it uses to invoke the handlers, operating on two axes:
We'll first address the traversal of the exception type hierarchy, then cover relative handler precedence.
Catch doesn't simply invoke handlers that match the exact type of the exception. Instead, it walks up and down the type hierarchy of the exception. It first notifies least specific handler in breadth first traversal mode, then gradually works down the type hiearchy towards handlers for the actual exception type, still in breadth first traversal. Once all breadth first traversal handlers have been invoked, the process is reversed for depth first traversal, meaning the most specific handlers are notified first and Catch continues walking up the hierarchy tree.
There are two modes of this traversal:
By default, handlers are registered into the DEPTH_FIRST traversal path. That means in most cases, Catch starts with handlers of the actual exception type and works up towards the handler for the least specific type.
However, when a handler is registered to be notified during the BREADTH_FIRST traversal, as in the example above, Catch will notify that exception handler before the exception handler for the actual type is notified.
Let's consider an example. Assume that Catch is handling the SocketException
. It will
notify handlers in the following order:
Throwable
(BREADTH_FIRST)
Exception
(BREADTH_FIRST)
IOException
(BREADTH_FIRST)
SocketException
(BREADTH_FIRST)
SocketException
(DEPTH_FIRST)
IOException
(DEPTH_FIRST)
Exception
(DEPTH_FIRST)
Throwable
(DEPTH_FIRST)
The same type traversal occurs for each exception processed in the stack trace.
In order for a handler to be notified of the
IOException
before the
SocketException
, it would have to specify the BREADTH_FIRST traversal path explicitly:
void handleIOException(@Handles(during = TraversalMode.BREADTH_FIRST)
CaughtException<IOException> evt)
{
System.out.println("An I/O exception occurred, but not sure what type yet");
}
BREADTH_FIRST handlers are typically used for logging exceptions because they are not likely to be short-circuited (and thus always get invoked).
When Catch finds more than one handler for the same exception type, it orders the handlers by precendence. Handlers with higher precendence are executed before handlers with a lower precedence. If Catch detects two handlers for the same type with the same precedence, it detects it as an error and throws an exception at deployment time.
Let's define two handlers with different precendence:
void handleIOExceptionFirst(@Handles(precendence = 100) CaughtException<IOException> evt)
{
System.out.println("Invoked first");
}
void handleIOExceptionSecond(@Handles CaughtException<IOException> evt)
{
System.out.println("Invoked second");
}
The first method is invoked first since it has a higher precendence (100) than the second method, which has the default precedence (0).
To make specifying precendence values more convenient, Catch provides several built-in constants, available
on the
Precedence
class:
To summarize, here's how Catch determines the order of handlers to invoke (until a handler marks exception as handled):
There are two APIs provided by Catch that should be familiar to application developers:
CaughtException
ExceptionStack
In addition to providing information about the exception being handled, the
CaughtException
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
CaughtException
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.
rethrow()
- 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 Catch is
rethrown. Does not mark the exception as handled.
handled()
- marks the exception as handled and terminates further handling.
proceed()
- default. Marks the exception as handled and proceeds with the rest of the
handlers.
proceedToCause()
- marks the exception as handled, but proceeds to the next cause in
the cause container, without calling other handlers for the current cause.
Once a handler is invoked it is muted, meaning it will not be run again for that exception stack trace,
unless it's explicitly marked as unmuted via the
unmute()
method on
CaughtException
.
ExceptionStack
contains information about the exception causes relative to the current
exception cause. It is also the source of the exception types the invoked handlers are matched against. It
is accessed in handlers by calling the method
getExceptionStack()
on the
CaughtException
object. Please see
API
docs
for more information, all methods are fairly self-explanatory.
This object is mutable and can be modified before any handlers are invoked by an observer:
public void modifyStack(@Observes ExceptionStack stack) {
...
}
Modifying the ExceptionStack may be useful to remove exception types that are effectively meaningless
sucsh as EJBException
, changing the exception type to something more meaningful such
as cases like SQLException
, or wrapping exceptions as custom application exception
types.
Stack traces are an everyday occurance for the Java developer, unfortunately the base stack trace isn't very helpful and can be difficult to understand and see the root problem. Catch helps make this easier by
The great part about all of this: it's done without a need for CDI! You can use it in a basic Java project, just include the Seam Catch jar. There are four classes to be aware of when using filtering
There's not much to this, pass it the exception to print and the filter to use in the constructor and call
printTrace()
which returns a string -- the stack trace (filtered or not). If no filter is
passed to the constructor, calling
printTrace()
will still unwrap the stack and print the
root cause first. This can be used in place ofThrowable#printStackTrace()
, provided the
returned string is actually printed to standard out or standard error.
This is the workhorse interface that will need to be implemented to do any filtering for a stack trace. It only
has one method:public StackFrameFilterResult process(StackFrame frame)
. Further below are
methods on
StackFrame
andStackFrameFilterResult
. Some examples are
included below to get an idea what can be done and how to do it.
This is a simple enumeration of valid return values for the
process
method. Please see the
API docs
for definitions of each value.
This contains methods to help aid in determining what to do in the filter, it also allows you to completely
replace the
StackTraceElement
if desired. The four "mark" methods deal with marking a stack
trace and are used if "folding" a stack trace is desired, instead of dropping the frame. The
StackFrame
will allow for multiple marks to be set. The last method,getIndex()
, will return the index
of the
StackTraceElement
from the exception.
Example 4.1. Terminate
@Override
public StackFrameFilterResult process(StackFrame frame) {
return StackFrameFilterResult.TERMINATE;
}
This example will simply show the exception, no stack trace.
Example 4.2. Terminate After
@Override
public StackFrameFilterResult process(StackFrame frame) {
return StackFrameFilterResult.TERMINATE_AFTER;
}
This is similar to the previous example, save the first line of the stack is shown.
Example 4.3. Drop Remaining
@Override
public StackFrameFilterResult process(StackFrame frame) {
if (frame.getIndex() >= 5) {
return StackFrameFilterResult.DROP_REMAINING;
}
return StackFrameFilterResult.INCLUDE;
}
This filter drops all stack elements after the fifth element.
Example 4.4. Folding
@Override
public StackFrameFilterResult process(StackFrame frame) {
if (frame.isMarkSet("reflections.invoke")) {
if (frame.getStackTraceElement().getClassName().contains("java.lang.reflect")) {
frame.clearMark("reflections.invoke");
return StackFrameFilterResult.INCLUDE;
}
else if (frame.getStackTraceElement().getMethodName().startsWith("invoke")) {
return StackFrameFilterResult.DROP;
}
}
if (frame.getStackTraceElement().getMethodName().startsWith("invoke")) {
frame.mark("reflections.invoke");
return StackFrameFilterResult.DROP;
}
return StackFrameFilterResult.INCLUDE;
}
Certainly the most complicated example, however, this shows a possible way of "folding" a stack trace. In the
example any internal reflection invocation methods are folded into a single
java.lang.reflect.Method.invoke()
call, no more internal com.sun calls in the trace.
Integration of Seam Catch with other frameworks consists of one main step, and two other optional (but highly encouraged) steps:
ExceptionToCatch
An ExceptionToCatch
is constructed by passing a Throwable
and
optionally qualifiers for handlers. Firing the event is done via CDI events (either straight from the
BeanManager
or injecting a Event<ExceptionToCatch>
and calling fire).
To ease the burden on the application developers, the integration should tie into the exception handling
mechanism of the integrating framework, if any exist. By tying into the framework's exception handling,
any uncaught exceptions should be routed through the Seam Catch system and allow handlers to be invoked.
This is the typical way of using the Seam Catch framework. Of course, it doesn't stop the application
developer from firing their own ExceptionToCatch
within a catch block.
An integration with Catch can define it's own handlers to always be used. It's recommended that any built-in handler from an integration have a very low precedence, be a handler for as generic an exception as is suitable (i.e. Seam Persistence could have a built-in handler for PersistenceExceptions to rollback a transaction, etc), and make use of qualifiers specific for the integration. This helps limit any collisions with handlers the application developer may create.
Catch supports qualifiers for the CaughtException
. To add qualifiers to be used when
notifying handlers, the qualifiers must be added to the ExceptionToCatch
instance via the
constructor (please see API docs for more info). Qualifiers for integrations should be used to avoid
collisions in the application error handling both when defining handlers and when firing events from
the integration.
ServiceHandlers make for a very easy and concise way to define exception handlers. The following example comes from the jaxrs example in the distribution:
@HandlesExceptions
@ExceptionResponseService
public interface DeclarativeRestExceptionHandlers
{
@SendHttpResponse(status = 403, message = "Access to resource denied (Annotation-configured response)")
void onNoAccess(@Handles @RestRequest CaughtException<AccessControlException> e);
@SendHttpResponse(status = 400, message = "Invalid identifier (Annotation-configured response)")
void onInvalidIdentifier(@Handles @RestRequest CaughtException<IllegalArgumentException> e);
}
All the vital information that would normally be done in the handler method is actually contained in the
@SendHttpResponse
annotation. The only thing left is some boiler plate code to setup
the Response
. In a jax-rs application (or even in any web application) this approach helps
developers cut down on the amount of boiler plate code they have to write in their own handlers and should
be implemented in any Catch integration, however, there may be situtations where ServiceHandlers simply do not
make sense.
CaughtException
, specifically abort()
, handled()
or rethrow()
. These methods affect invocation of other handlers (or rethrowing the
exception in the case of rethrow()
).
An exception chain is made up of many different exceptions or causes until the root exception is found at the bottom of the chain. When all of the causes are removed or looked at this forms the causing container. The container may be traversed either ascending (root cause first) or descending (outer most first).
A CDI enabled Bean which contains handler methods.
Annotated with the @HandlesExceptions
annotation.
See Also Handler Method.
A method within a handler bean which is marked as a handler using the @Handlers on an argument, which must be an instance of CaughtException. Handler methods typically are public with a void return. Other parameters of the method will be treated as injection points and will be resolved via CDI and injected upon invocation.