Tuesday, 6 October 2009

Exceptions in Java world: agree or not to agree, this is the question

1.     Introduction 
In this article I try to explain generic rules or tips that can be followed to try to create a robust application with decouple layers. This topic is very polemic because everybody has a particular opinion about this and actually there is no a golden rule to follow. 








2.     Exceptions
The Java programming language provides three kinds of throwables:

2.1  Checked exceptions
Checked exceptions are all subclasses of java.lang.Exception, except java.lang.RuntimeException and its subclasses.

2.1.1       Business exceptions
The business exceptions should be used for exceptional conditions and not for ordinary control flow. A well-designed API must not force its client to use exceptions for ordinary control flow.

The business exceptions are used for conditions from which the caller can reasonably be expected to recover. It is important to mention that the business exceptions are associated with a business semantic.

Avoid unnecessary use of business exceptions. The overuse of checked exceptions can make an API far less pleasant to use. If a method throws one or more checked exceptions, the code that invokes the method must handle the exceptions in one or more catch blocks, or it must declare that it throws the exceptions and let them propagate outward. Either way, it places a nontrivial burden on the programmer.

Example
public void transferMoney(AccountDTO accountTo, AccountDTO accountFrom , double amount) throws NoSufficientFundsException

Exception in business meaning:

NoSufficientFundsException is thrown if you try to transfer money from an account to another account and you have no enough money.

This checked exception bring business semantics to the caller.

2.1.2       ‘Infrastructure’ Checked exceptions
For example, we could consider as ‘infrastructure’ checked exceptions J2SE API checked exceptions, JEE API checked exceptions, Third Party API checked exceptions etc.

For instance:

§          java.io.IOException
§          javax.naming.NamingException
§          java.sql.SQLException.
§          javax.mail.MessagingException
§          javax.resource.ResourceException
§          javax.ejb.FinderException.

If an ‘infrastructure’ checked exception is thrown, it can be catch and either a business exception will be thrown up or it will be logged.

Note: The high-level classes should not know the low-level details of implementation.

Example 1.
       // Exception Translation with infrastructure checked exceptions
       try {
       // ...
       } catch (IOException e) {
                     throw new NotFoundResourceException();
}

Example 2.
       // Exception log
       try {
       // ...
       } catch (IOException e e) {
              log.error(e,”The resource was not found”);
              … // solve the problem somehow. 
       }

2.2  Unchecked exceptions

2.2.1       Runtime exceptions
Runtime exceptions are all subclasses of java.lang.RuntimeException.

The runtime exceptions should not be thrown explicitly in developer java code. These exceptions mean precondition violations or bugs. This is simply a failure by the client of an API to adhere to the contract established by the API specification.

2.2.2       Errors
All subclasses of java.lang.Error.

There is a strong convention that errors are reserved for use by the JVM to indicate resource deficiencies, invariant failures, or other conditions that make it impossible to continue execution. It is very recommended to extend the error class.

3.     Advisable Rules

3.1  Throw exceptions appropriate to the abstraction.
It is disconcerting when a method throws an exception that has no apparent connection to the task that it performs. This often happens when a method propagates an exception thrown by a lower-level abstraction. This pollutes the API of the higher layer with implementation details. If the implementation of the higher layer changes in a subsequent release, the exceptions that it throws will change too, potentially breaking existing client programs.
To avoid this problem, higher layers should catch lower-level exceptions and, in their place, throw exceptions that can me explained in terms of the higher-level abstraction. This is known as exception translation.

Example 1.
              // Exception Translation
       try {
              // Use lower-level abstraction to do our bidding
             
       } catch (LowerLevelException e) {
              throw new HigherLevelException(...);
       }
     
A special form of exception translation called exception chaining is appropriate in cases where the lower-level exception might be helpful to someone debugging the problem that caused the higher-level exception. The lower-level exception (the cause) is passed to the higher-level exception, which provides an accessor method (Throwable.getCause) to retrieve the lower-level exception:

Example 2.
       // Exception Chaining
       try {
              // Use lower-level abstraction to do our bidding
             
       } catch (LowerLevelException cause) {
              throw new HigherLevelException(cause);
       }
     
The higher-level exception’s constructor passes the cause to a chaining-aware superclass constructor, so it is ultimately passed to one of Throwable’s chaining-aware constructors, such as Throwable(Throwable):

Example 3.
//Exception with chaining-aware constructor
              Class HigherLevelException extends Exception {
                     HigherLevelException(Throwable cause){
                            super(cause);
                     }
              }

Most standard exceptions have chaining-aware constructor. For exceptions that don’t, you can set the cause using Throwable’s initCause method.

3.2  Never declare that a method throws Exception or worse yet, throws Throwable.

3.3  Include failure-capture information in detail messages.
To capture the failure, the detail message of an exception should contain the values of al parameters and fields that “contributed to the exception”.

Example 1.
// Including detail message about the exception
              try {
                     // Use lower-level abstraction to do our bidding
                    
              } catch (LowerLevelException cause) {
                     throw new HigherLevelException(“This the cause”,cause);

Example 2.
// Including detail message about the exception
throw new LowerLevelException(“This the cause”);




4.     Case studies – Bank Website
In this section I show different use cases with different perspective about how the exceptions are focus. We start thinking that we have an application in the way that is represented in the figure.







4.1  Use case – Balance


Client Component

try {
      
Double balance = façadeBank.balanceAccount(userDTO);  
       …
} catch (RuntimeException e) {
       log.error(e,”Some message”);                          
       forward = “error_page”;                        

}

return forward;

The RuntimeException can be catch in the client component but it should not be catch in lower layers. In this example in case of RuntimeException I am forwarding the output to an error page.

4.2  Use case - Transfer money

Client Component
            try{
              …     
                     façadeBank.transferMoney(accountFrom, accountTo, amount);
                    

              } catch (TransactionMoneyException e){
                     log.error(e,”Some message”);
                     addMessageUI(“It was no possible to transfer money.”);

       }

If the TransactionMoneyException is thrown in the client component, I add a message to the user interface reporting that it was an error trying to transfer money.
      
Façade
      
       void transferMoney (AccounDTO fromAccount, AccounDTO accountTo, Double amount) throws TransactionMoneyException {      

              try {
                     AccountAppService.transferMoney(fromAccount, toAccount,amount);
               
              } catch (TransferTransactionException e) {
                           throw new TransactionMoneyException (“No Money sent");

              }
       }

If the TransferTransactionException is thrown I translate to a more readable transaction TransactionMoneyException and throw up to the caller.

ApplicationService

public void transferMoney(AccountDTO fromAccount, AccountDTO toAccount, Double money) throw TransferTransactionException {
      
       try {
              …            
              thirdPartyAPIObject.createBankTransaction(…);
                    
} catch (ThirdPartyConnectionException e) {
              log.error(e, “Transfer Transaction aborted”);
              throws new TransferTransactionException()
       }


The ThirdPartyConnectionException is a checked exception thrown by thirdPartyAPIObject object when we execute the method createBankTransaction. This exception is low level therefore I translate to higher level checked exception.





4.3  Use case - Withdraw money

Client Component

       try{
              …     
              façadeBank.withdrawMoney(account);
             
       } catch (TransactionMoneyException e){
              log.error(e,”Some message”);
              addMessageUI(“Some UI error message”);
       }
       …
If the TransactionMoneyException is thrown in the client component, I add a message to the user interface reporting that it was an error trying to transfer money.


Façade

void withdrawMoney (AccountDTO account, Double amount) throws TransactionMoneyException {             
       try {
             
              TransferOperationAppService.withdrawMoney (account, amount);
             

       } catch (NoFundsException e) {
              throw new TransactionMoneyException (“No money in the account");
       }
       
}

If the NoFundsException is thrown I translate to a more readable transaction TransactionMoneyException and throw up to the caller.

ApplicationService

public void withdrawMoney (AccountDTO account, Double amount) throws NoFundsException {
             
              throw new NoFundsException();
             
}     


This method throws checked exception NoFundsException when there is not money in the account.

4.4  Use case – Checking personal details.

ApplicationService

       public void checking (UserDTO user) {
             
              try {
                    
                     somethingThrowsSQLException
                    
                    
} catch (SQLException e) {
                     log.error(e, “It is not possible to check the details.”);
                    
}     


       }

In this example a ‘infrastructure’ checked exception is thrown. I log the error.


9 comments:

  1. interesting article!!!

    3.1 "exception transalation". now i know how to call what i usually do, i.e. when "low-level" exception is not accessible by classes in "client component" because they are deployed in a different bundle.

    4.4 should not "checking" throw a RuntimeException here? If that's the case, I would remove logging...

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. As I said, there is no a golden rule for the exceptions.
    I believe we should not throw any RuntimeException, because they mean bugs or development errors.

    What I want to mean with the 4.4 is that sometimes we can catch check exceptions (business or infrastructure), log it and try to figure the problem out without throwing up a business exception.

    ReplyDelete
  4. Just being somehow pragmatic, I would throw RuntimeExceptions, even if not such business exceptions, when those are in "bottom" layers, i.e. data access. Otherwise they would be propagated in all your method signatures up until you manage them.

    See new HibernateException included in version 3.

    ReplyDelete
  5. In my opinion I prefer to throw a business exception up because we are forcing the caller to understand the contract with the method called.
    In the meantime RuntimeException means bugs. Is this a bug or is possible situation?.

    ReplyDelete
  6. You're right regarding you're developing that code for a particular application and tied to its business.

    See the DAO can be a generic DAO and just the basic CRUD operations do not need specific business exceptions each of them, right?

    ReplyDelete
  7. Pablo, my friend!, go to this post

    Four J2EE Patterns - Will we be able to understand them definitely? ......

    and never talk in front of me about DAO when this should be Application Service.

    From my experience, I dont want to critised hibernate generic DAO but I am still thinking that is not very useful for complex JEE applications.

    Because when we are working in applications where the Business objects need low level particular checking, these generic DAO methods make no sense for me.

    ReplyDelete
  8. mmm... you want to me read another post, don't you? ;)

    in other words, i would not throw a different exception for each CRUD operation in my DAOs, generic or not, Hibernate-inhereted or not...

    But whichever the data access technology I would use, I like the RuntimeException solution. I've got freedom to establish the convention about which boundaries/layers I'll catch MyDataAccessRutimeException within.

    RutimeException vs. translating them, the decission is not only driven by their nature, whether infrastuctural or business, but also about decoupling and keep code clear and simple.

    ReplyDelete
  9. 1. I do not propose to use a different exception for each method in the ApplicationService that works with a BO.

    2. I rather use @ApplicationException(rollback=true) instead of having MyDataRuntimeException.
    The efect is the same and you can have the control of the contract that the caller must follow.

    ReplyDelete