Handing exceptions with asynchronous services
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:- Use typed exceptions
- Provide information about the exceptions and ways to handle them
/**
* 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.
Thursday 17 Nov 2005 | Stéphane | Java