EJB Exception Handling

It is striking that most search results for the keywords “EJB” and “exceptions” point to the same few articles at IBM DeveloperWorks. Unfortunately, they are too advanced for beginners. This post is a simple guide to handling exceptions in EJB.

Definitions

A checked exception is derived from java.lang.Exception but is not a subclass of java.lang.RuntimeException.

An unchecked exception is derived from java.lang.RuntimeException and is thrown by the Java Virtual Machine (JVM).

A system exception is thrown by the system and is not recoverable. For example, an EJB container, losing its connection to a database server, causes a system exception; similarly, a failed remote object method call causes a system exception.

An application exception is specific to the application and is thrown according to business rules. For example, an application exception is raised when the balance for a savings account becomes negative.

System Exception

A system exception is unchecked and is derived from java.lang.RuntimeException. Because of its unpredictability, it must be handled by the EJB container.

A container wraps an EJBException in a RemoteException and throws it to the client. Although some information about the error is lost, the RemoteException is sufficient because the client cannot recover from a system error.

Therefore, for an exception to be caught by the container and sent to the client, a developer must wrap it in an EJBException. The following code extract demonstrates this.

...
public String ejbCreate(String id, String username, String password) 
    throws CreateException {

    try {
        this.id = id;
        this.username = username;
        this.password = password;

        insertRecord();
    } catch (SQLException e) {
        throw new EJBException(e); // swallow the SQLException
    } catch (Exception e) {
        throw new EJBException(e);
    }
    return id;
}
...

Since an EJBException is a system (and unchecked) exception, it does not  have to be declared in the throws clause.

create(...) is a delegate method of the Home object created by the container, and if the method fails due to a system error, the container throws a RemoteException. Therefore, in the Home interface of the remote object, the create(...) method must throw RemoteException, like this:

...
public UserAccount create(String id, String username, String password) 
    throws RemoteException;
...

The client code then looks like this:

...
try {
    UserAccount userAccount = 
        userAccountHome.create("1001", "user", "password");
} catch (CreateException e) {
    System.out.println("Object could not be created");
} catch (RemoteException e) {
    System.out.println("EJB Error: " + e.getMessage() );
} catch (Exception e) {
    e.printStackTrace();
}
...

In this example, the container notifies the client about a system exception by way of the RemoteException.

Application Exception

An application exception is checked and, therefore, must be declared in a throws clause. It is thrown explicitly by code to indicate an error linked to the business rules. For example, debiting a sum of money that makes a savings account balance go negative triggers a SavingsAccountDebitException. An application exception is needed for a client to be alerted of an error, to determine what caused it, and to notify users. An EJB container does not intercept an application exception because it does not know how to handle it. Consequently, the container does not log an application exception be default.

A developer does not need to wrap such an application exception in an EJBException. In fact, to prevent the container from hi-jacking the exception, a developer must not do this.

Below is a code extract that shows a typical application exception handling.

...
public void debit(double amount) 
    throws SavingsAccountDebitException {

    if (this.balance == 0) {
        throw new SavingsAccountDebitException(
            "Cannot make a debit");
    }

    try {
        balance -= amount;
        updateRecord();
    } catch (SQLException e) {
        throw new EJBException(e);
    } catch (Exception e) {
        throw new EJBException(e);
    }
}
...

Notice that when an EJBException is thrown, it can be left out in the throws clause. Also, this code does not demonstrate how transactions are rolled back, a topic which is covered later in this post.

In the remote interface for the bean containing the above code, a throws clause declaring SavingsAccountDebitException is needed, as in the following.

...
public void debit(double amount) 
    throws SavingsAccountDebitException;
...

The client code using the remote interface could look like this:

...
try {
    savingsAccount.debit(1000.00);
} catch (SavingsAccountDebitException e) {
    System.out.println(e.getMessage() );
} catch (Exception e) {
    e.printStackTrace();
}
...

Observe that the application exception is successfully propagated to the client and is ignored by the container. The client uses the information from the exception to notify users; in this example, it displays an error message.

Exceptions and Transactions

How transactions are managed is affected by how exceptions are handled.

A transaction managed by the container is automatically rolled back when a system exception occurs. This happens because the container intercepts system exceptions. However, when an application exception occurs, the container does not intercept it and, therefore, does not roll back a transaction.

The following code extract illustrates this point.

...
EntityContext ctx;
...
public void ejbRemove() 
    throws RemoteException {
    try {
        deleteRecord();
    } catch (SavingsAccountDeleteException e) {
        ctx.setRollbackOnly()
        throw new RemoveException(e.getMessage() );
    } catch (SQLException e) {
        throw new EJBException(e);
    } catch (Exception e) {
        throw new EJBException(e);
    }
}
...

Conclusion

This post applies to remote EJB; when local EJB is  used, RemoteException is never thrown.

As shown, handling exceptions in EJB is different to how it is done in standalone Java applications. In fact, the guidelines for dealing with each situation can be contradictory in some cases. For a comparison, please refer to the post Exception Handling Best Practice.

These are the four rules to follow for proper handling of exceptions in EJB:

  • If you cannot recover from a system exception, let the container handle it.
  • If a business rule is violated, throw an application exception.
  • Catch exceptions in the order in which you can handle them. Alternatively, catch them in descending order of likelihood of them occurring.
  • Always catch a java.lang.Exception last.

PHP collection class

This is a simple PHP class to manage a collection of items.

Refer to the sample at the end for usage example.

Collection.php:
<?php
class Collection {
    var $elements = array();
    var $counter = 0;
    var $pointer = 0;

    function Collection() {

    }

    function add($element) {
        $this->elements[$this->counter] = $element;
        $this->counter++;
        $this->pointer++;
    }

    function remove($element) {
        $found = null;
        for ($i = 0; $i < count($this->elements); $i++) {
            if ($this->elements[$i] == $element) {
                $found = $i;
            }
        }
        if ($found != null) {
            array_splice($this->elements, $found, 1);
            $this->counter--;
            $this->pointer--;
        }
    }

    function contains($element) {
        for ($i = 0; $i < count($this->elements); $i++) {
            if ($this->elements[$i] == $element) {
                return true;
            }
        }
        return false;
    }

    function hasNext() {
        return $this->pointer < $this->counter;
    }

    function hasPrevious() {
        return $this->pointer > 0;
    }

    function next() {
        return $this->elements[$this->pointer++];
    }

    function first() {
        $this->pointer = 0;
        return $this->elements[$this->pointer];
    }

    function last() {
        $this->pointer = $this->counter;
        return $this->elements[$this->pointer];
    }

    function previous() {
        return $this->elements[--$this->pointer];
    }

    function count() {
        return count($this->elements);
    }
}
?>
Example usage:
<?php
require_once "Collection.php";

$collection = new Collection();

for ($i = 0; $i < 10; $i++) {
    $element = "Element $i";
    $collection->add($element);
}

$collection->first();
while ($collection->hasNext() ) {
    $element = $collection->next();
    echo $element . "n";
}

echo "n";

$collection->previous();
$collection->previous();
$collection->remove("Element 7");

$collection->last();
while ($collection->hasPrevious() ) {
    $element = $collection->previous();
    echo $element . "n";
}

?>