One of the great feature of Java2 Enterprise Edition is its messaging system, defined by the Java Message Service specification.

I won’t explain the basics of JMS, please check this tutorial for more details. There are good materials regarding JMS on the Internet but I think that exception handling is not correctly covered.

How can we handle exceptions in services invoked asynchronously? Well, I would say that this fall into two main principles:

  1. Use typed exceptions
  2. Provide information about the exceptions and ways to handle them

Let’s say that a service is invoked with an instruction stored as a JMS message. A pattern defines this scenario, see the Service Activator pattern (Gosh, I just realized that I was stupid enough to buy a book whose content is on the Internet - shame on me!). Basically, it says that the Message Driven Bean should simply create the Instruction from the JMS message and invoke some business component with the instruction. Let’s KISS, shall we? (Keep It Stupid and Simple, stop dreaming).

Now, the processing of the instruction by the business component might fail for lots of reasons. The first thing to do is to type your exceptions in order to know whether it is recoverable or not. Unrecoverable exceptions are, for instance, an invalid instruction, a missing component needed to process the instruction, etc. Those means that in any case the instruction could not be processed with the data currently available. Recoverable exceptions are, for instance, a failure to access a J2EE component, a database error or timeout, a connection timeout with the JMS server, etc. Exceptions could even be subsclassed so that you can provide a specific message about the exception. Based on this, a single entry point for exceptions handling can be defined as follows:


/**
* Handles recoverable exceptions.
*
* @param inst the instruction that could not be processed
* @param props the jms message properties
* @param context the reason why the instruction is refused
* @param t the exception
*/
protected void handleError(Instruction inst, Properties props,
String context, Throwable t) {
// Does not throw any exception
rejectedInstructionHandler.
processRejectedInstruction(inst, props, context, t);
}

The message driven bean should only invoke the business component with the message and handle JMS specific exceptions as well as exceptions while invoking the business component itself.

public void onMessage(Message msg) {
try {
// Get the representation of the message - assume that TextMessage is sent
String xmlInstruction = ((TextMessage) msg).getText();
// jmsProperties contains the message properties and the destination
// where it was received
instructionHandlerHome.create().
processInstruction(xmlInstruction, jmsProperties);
} catch (JMSException e) {
logger.error(”Failed to get message content”, e);
ctx.setRollbackOnly();
} catch (CreateException e) {
logger.error(”Could not create instruction handler instance”, e);
ctx.setRollbackOnly();
} catch (EJBException e) {
logger.error(”Unexpected error”, e);
ctx.setRollbackOnly();
}
}

The business component should catch each and every exception with an appropriate error message. For instance:

public void processInstruction(String xmlInstruction, Properties properties) {
Instruction instruction = null;
try {
// Get the instruction
instruction = instructionParser.parseInstruction(xmlInstruction);
// process the instruction
} catch ( InvalidInstructionException e) {
handleError(currentInstruction, properties,
“Invalid instruction”, e);
} catch ( InstructionValidationException e) {
handleError(currentInstruction, properties,
“Validation of the instruction failed”, e);
} catch ( ProcessorException e) {
handleError(currentInstruction, properties,
“Could not process the instruction”, e);
} catch (RuntimeException e) {
handleError(currentInstruction, properties,
“System exception”, e);
}
}

In order to provide information about the exceptions and ways to handle them, a good solution is to post messages which could not be processed to an error queue with additional information such as: error message, stacktrace, processing date, etc. One could implement a JMS browser on this queue with the ability to inspect the message which failed and why they failed, based on the information which has been added while handling the unrecoverable exception. If you provide a minimum of standardized information in the original request, one could also provide the ability to repost the message! Simple, just provide the JNDI name of the queue where the message was initially posted.

If the exception could not be recovered, a good behavior is to rollback the transaction in order to retry later on. If a message could not be processed after some time, it becomes a poison message and will be sent as a result to an error queue. A good practice in this case is to configure the system so that this error queue is the same as the one used for recoverable exceptions (With JBoss, you need to define a new invoker-proxy-binding and configure your MDB to use it, check this page for more details).

This architecture allows you to see recoverable exceptions and unrecoverable exceptions! Moreover, if the exceptions were linked to an external problem which has been solved meanwhile, you can simply repost all messages in order to process them.