Apr 18, 2013

Exceptions and Transactions in EJB, dealing with EJBException and EJBTransactionRolledbackException

When an exception is thrown from an EJB, several things work very differently from what you got used to in Java SE environment or basically outside of the EJB scope.

Things you have to consider:
  • Some exceptions thrown from the EJB scope trigger transaction rollback, other ones don't
  • Some exceptions coming from EJB get wrapped in an EJBTransactionRolledbackException, others don't
  • Whether or not the exception came from "nested" EJB method calls

Basic setup

You call an EJB method that has it's transaction attribute set to Required or RequiresNew. Two states distiquish this scenario from other scenarios: transaction is different from caller's transactional state, there is a transaction on the callee side.

Unchecked (java.lang.RuntimeException) exceptions

When a java.lang.RuntimeException is thrown from the myClientEjbMethod() method, the container will 1) roll back the transaction 2) discard the EJB Bean instance 3) Throw a javax.ejb.EJBException to the client. Every database changes made in your EJB call will be rolled back.

When you know what kind of exceptions you expect in your web layer, you have to unroll the EJBException to dig into the real reason, and show it to the user if it's meaningful to her. These kind of exceptions would be specialized runtime exceptions you defined (let's say BusinessValidationException extends RuntimeException).

Digging into the real reason is a simple method:

Throwable unrollException(Throwable exception,
  Class<? extends Throwable> expected){

  while(exception != null && exception != exception.getCause()){
    if(expected.isInstance(exception)){
      return exception;
    }
    exception = exception.getCause();
  }
  return null;
}

Checked (java.lang.Exception) exceptions

When a java.lang.Exception is thrown from the EJB method, you obviously have to declare it in the method's signature with the throws keyword. What happenes in this case as follows 1) the transaction gets commited 2) the exception is rethrown to the client. ...Yes, that's it. No rollback occurs, and the client sees the exact same exception as was thrown. In EJB terms a checked exception is an ApplicationException. The name indicates that this exception means a problem, the application developer is aware of. I can think of two types I would define for my application:
  • BusinessValidationException extends java.lang.Exception
  • BusinessLogicException extends java.lang.Exception
Because you, as the application developer throw the ApplicationException on purpose, you have to decide   if the transaction has to be rolled back, or the flow can be continued, and maybe throw the exception at the end of the method to indicate some problem happened during processing but it actually executed successfully.

To force the rollback manually for an ApplicationException, before throwing it, you have to get a reference to the EjbContext, and call EjbContext.setRollbackOnly().

You also have to be conscious about whether you want to just rethrow a FileNotFoundException, ParseException checked exceptions from various utilities. They don't cause a rollback, when simply rethrown from the EJB method, because they have to be present on the method signature, therefore are ApplicationExceptions. The EJB specification recommends that in this case, you wrap these exceptions into a javax.ejb.EJBException. That is because these are likely to be errors out of the developers scope, and you cannot continue the transaction, so you just rethrow it packaged in an EJBException runtime exception: becaus runtime exceptions do cause rollback.

For example:


Date getDateFromStringEjbMethod(String yearString){
  SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY");
  try{
    return dateFormat.parse(yearString);
  } catch(ParseException e){
    throw new EJBException(e);
  }
}

Design your business logic exceptions

I feel it to be a burden to call EjbContext.setRollbackOnly() every time I throw my little BusinessValidationException. I might also forget to call it, resulting in a commit and inconsistent DB state.
You have three sensible options to define exception types that mean an error in the business flow:
  1. Inherit your BusinessValidationException from java.lang.RuntimeException:
    Transaction is rolled back automatically, but you have to unroll the exception on client side, because it will be wrapped into an EJBException
  2. Inherit your BusinessValidationException from java.lang.RuntimeException (same as previous), and annotate this class with @ApplicationException(rollback=true)
    What you gained is, you will get the same exception on client side, not a wrapper, and the transaction is still rolled back.
  3. Inherit from java.lang.Exception and annotate it with @ApplicationException(rollback=true)
    This is the same as 2) except, you can warn the client to be aware of a business kind of exception to handle in a user-friendly way.
  4. 2) or 3) with rollback=false attribute for problems where the transaction can be continued/committed, but an indication should be sent to the user that there was a problem
When I throw business workflow error or validation kind of exceptions from my EJB methods, I always try to define a meaningful message, not a very technical one and show it to the user, instead of letting the exception bubble up, and let the servlet container show the standard error message.


Exceptions from nested EJB calls



When you call myOtherEjbMethod() from myEjbMethod(), things change a little. The container's behavior for myOtherEjbMethod() will be the same for the ApplicationException and RuntimeException as described previously i.e.: If an ApplicationException is thrown, you have to decide if the myOtherEjbMethod() will be rolled back or not. If  a runtime exception occurs, the transaction gets rolled back regardless (If that RuntimeException is not annotated with @ApplicationException).

But what will you see in the caller myEjbMethod()? In nested calls, myEjbMethod() is the client, and the container may wrap the actual exception into an javax.ejb.EJBException or javax.ejb.EJBTransactionRolledbackException. The rules are (I think) intuitive after you learned the behavior in the simple case in the previous section.

When myOtherEjbMethod() uses the client's ( myEjbMethod() )  transaction, the container will wrap the RuntimeException thrown from nested method into the javax.ejb.EJBTransactionRolledbackException. Why? Because this way you will know, that continuing the transaction in myEjbMethod() is "fruitless" (as the EJB 3.1 specification pens it)

When myOtherEjbMethod() uses a new transaction (with RequiresNew transactional attribute), in case of a RuntimeException, the container will wrap it in a EJBException. You will know, that it caused the new transaction to roll back, but you can continue the outer transaction. This is actually what RequiresNew is good for.

Sum it up

EJB makes a difference in Application Exceptions and System Exceptions. Application exception is something that you define, you throw, and you are aware of. By default the application exception does not cause a rollback, unless you define it that way (and I think it's recommended). Every checked exception that is mentioned in the method signature and also any checked or unchecked exception that is annotated with @ApplicationException, is an application exception.

System exceptions happen in cases, you don't control, and they are unchecked exceptions. They always cause rollback. Good practice is, if you wrap checked exceptions -- that cannot be avoided -- in your method into EJBException e.g. ParseException.

12 comments:

  1. Nice explanation. Really loved your write up.

    ReplyDelete
    Replies
    1. Thank you, I was wondering how understandable this post is. For me it did not turn out to be so clear and linear. But I am glad, you found it useful.

      Delete
  2. "When myOtherEjbMethod() uses a new transaction (with RequiresNew transactional attribute), in case of a RuntimeException, the container will wrap it in a EJBException. You will know, that it caused the new transaction to roll back, but you can continue the outer transaction"

    The EJB spec says that in this case the transaction may or may not be rolled back.

    Please review EJB Spec, page 386, table 15. and section 14.4.2.

    ReplyDelete
    Replies
    1. Hi, I see your point. It is ambiguous to me that 'may or may not be marked for rollback' is whether up to the container or up to the client code.

      It would be interesting if they made this decision up to the implementation, as this sentence does not say anything else about the condition.

      What is your opinion?

      Delete
  3. It would be interesting to get your opinion on Seam Catch framework and how it fits into this model.

    Cheers,
    Andy Z.

    ReplyDelete
    Replies
    1. Hi,

      I haven't tried Seam Catch, so I cannot make any comments about it. I think you cannot affect with interceptors the way how the container handles exceptions.

      Delete
  4. Excellent post. Thank you very much.

    ReplyDelete
  5. Really enjoyed reading your post. Very clear explanation of this topic. Thank you

    ReplyDelete
  6. Really a very nice article. Thanks Pal :-).

    ReplyDelete
  7. Excellent post! Congratulations. Thanks man!

    ReplyDelete
  8. You could handling the Application Exception using the @ApplicationException too. With this annotation, you could to define if the Application Exception must be rolled back or not, in this way:

    @AnnotationException(rollback="true")
    public class MyAppException extends Exception{ //some code here}

    If the MyAppException is thrown, then the transation will be rolled back. If the rollback parameter is set to "False", then the transaction won't be rolledback. In this way you don't have to extend from RuntimeException for a application exception. I think it's better in this way and more elegant.

    ReplyDelete
  9. thank you very much, it's really excellent post!

    ReplyDelete