Exception handling and EJBs

Exception handling is essential to any robust application. Understanding what constitutes a good exception handling technique is important in creating robust applications. In this recipe, we will examine several of these techniques, and see how we can apply them to Java EE applications.

Knowing where to handle exceptions is the key to the proper use of exception handling. The organization of the exception handling code is dependent upon the structure of the application. Exception handling can be viewed from a development and a production standpoint, both of which are important.

When an exception occurs in an EJB that it cannot recover from, it is typically wrapped in an EJBException and then thrown to the caller.

Getting ready

Exceptions are not something we want to avoid, but rather we should embrace them as another tool in our arsenal to create good applications. Exceptions will occur. They may be the result of user input, system problems such as a network being down, or a server crashing, or simply events that were not anticipated during the development of the application.

When an exception occurs, the developer can either have the containing method throw the exception to the caller or handle the exception using try-catch blocks. Throwing the exception back to the caller is appropriate when the caller is in a better position to handle the exception. If the exception can be handled within the method, then try-catch blocks should be used.

In this recipe, we are concerned with how to structure and handle exceptions we catch. However, we may still return the exception to the caller. The basic approach is to use a try block followed by one or more catch blocks, and optionally, a finally block.

...
try {
// Attempt to execute code which might throw an exception
}
catch(Exception1 e) {
//Handle execption1
}
catch(Exception2 e) {
//Handle execption2
}
...
finally {
// Clean up actions
}
...

The order of the catch blocks is important. The base-most exception should be listed last since the first catch clause that matches the exception will handle the exception thrown. If the base-most exception is listed first, the other derived exceptions will never be executed. A compile-time error may be generated in some situations depending on the inheritance relationship between the exceptions.

How to do it...

Let's examine a situation where we inadvertently lose information about an exception. Consider the following catch block which recasts the exception as an EJBException. The EJBException single string argument is used to pass an error message describing the exception.

catch(Exception e) {
throw new EJBException("Some Exception");
}

The problem here is the name of the original exception and where in our code the exception occurred is unknown. It is possible the exception could have been generated at more than one point, and understanding where the exception occurred can be important in recovering from the exception.

A better technique is to wrap the exception context information inside the new exception.

catch(exception e) {
throw new EJBException(e);
}

A variation of this is to use the toString method when recasting the exception.

catch(exception e) {
throw new EJBException(e.toString());
}

The usefulness of this technique depends on what is returned by the toString method. The getMessage method could have been used but it may not provide much more information than the toString version.

catch(exception e) {
throw new EJBException(e.getMessage());
}

Frequently, the best approach is to propagate the message and the exception as follows:

catch(exception e) {
throw new EJBException(e.getMessage(),e);
}

How it works...

We saw that it is important to avoid losing information about an exception. The first example illustrated how the name of the exception can be lost along with its location. Using the toString method to retrieve exception information was shown not to be a reliable technique. The last example illustrates a better technique where the getMessage method was used to convey the essence of the problem and the exception itself was propagated to the calling method.

There's more...

There are other aspects to exception handling including:

  • Logging exceptions to the console
  • Incomplete exception handling
  • Exceptions that are ignored

Logging exceptions to the console

Problems can occur when exception information is written to the console.

catch(exception e) {
e.printStackTrace();
throw new EJBException(e.getMessage(),e);
}

The problem here is that the console may not always be available. During the development process, it may work fine. The development server and the application may actually reside on the same machine or the server is easily accessible. In a production environment we may not have access to the server. In addition, the administrators of the server may have redirected System.out and System.err output to a null file. Worst yet, we may not even know where the server resides.

Logging is a better technique for reporting exceptions as illustrated here:

catch(exception e) {
log.error(e.getMessage), e);
throw new EJBException(e.getMessage(),e);
}

Even better, let the caller decide whether or not to log the exception. This can avoid situations where the same exception is logged twice, once in the current method and again by the caller. In this situation, we may want to get rid of the whole try-catch block and have the current method throw the exception.

Incomplete exception handling

Exception handling can be complicated at times. Consider the following sequence where two files are used:

try {
inputFile = new FileInputStream(someFile);
outputFile = new OutputFileStream(someOtherFile);
...
}
catch(exception e) {
...
}
finally {
try {
inputFile.close();
outputFile.close();
}
catch(IOException e) {
// Handle exception
}
}

When using system resources such as files, it is important to release them. In the case of files, they should be closed when the application no longer needs them. The above sequence attempts to close the files whether an exception has occurred or not.

There are a couple of problems with this approach. First, it is possible that the attempt to create the input file will throw an exception. When this occurs, executing the outFile.close() statement will throw a NullPointerException. Second, if the inputFile.close() statement throws an exception, it will skip the outputFile.close() statement.

A better way of handling this situation is shown here:

try {
inputFile = new FileInputStream(someFile);
outputFile = new OutputFileStream(someOtherFile);
...
}
catch(exception e) {
...
}
finally {
try {
if(inputFile != null) {
inputFile.close();
} catch(IOException e) {
// Nothing to do
}
}
try {
if(outputFile != null) {
outputFile.close();
} catch(IOException e) {
// Nothing to do
}
}
}

Both file reference variables are checked separately to determine if they are null. If they are not, then the file is closed. If an exception occurs trying to close the file, then there is little we can do. Even with this example, depending on how the first catch block is written, we may not be able to determine which line caused the exception.

While we have focused on an IO sequence, any set of operations which require cleanup performed in a finally block, should be examined carefully to determine if the exception handling approach used is complete.

Exceptions that are ignored

Sometimes we may be tempted not to handle or propagate exceptions we know will never be thrown. We may consider the likelihood of the exception occurring so unlikely that if it did happen, we wouldn't know what to do anyway.

try {
...
}
catch(SomeException e) {
// Do nothing
// This should never happen
}

While the current environment may never generate the exception, who knows what may happen in the future when things change. The implementation of the code throwing the exception may change resulting in the exception being thrown. In addition, we may be wrong and in a very rare set of circumstances the exception may actually be thrown.

If we ignore the exception, then the exception is lost, potentially making it much more difficult to find the error in our application. A better approach is to catch the exception and then throw a runtime exception.

try {
...
}
catch(SomeException e) {
// Should never happen
throw new RuntimeException(e.getMessage(),e);
}

In addition, it may be desirable to log the exception. This will provide yet another avenue to help us determine the nature of the problem.

See also

The Using an interceptor for logging and exception handling recipe uses interceptors to handle exceptions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.72.245