JavaExceptions: The Missing Link Arrives for Java

Exceptions: The Missing Link Arrives for Java

A robust program is one that is aware of potential pitfalls and has ways and means to recover from them. These pitfalls could be some boundary condition, like ‘array boundaries’, ‘negative age’, ‘uninitialized objects’, etc., or could be some conditions beyond the control of the programmer, like ‘requested file not found’, ‘database failed to execute a SQL query’, and so forth.

Fortunately, Java provides a very clean object-oriented framework for exception management and thus simplifies, almost standardizes, the chores of a programmer. New features have been added to the Exceptions Framework in Version 1.4 of the Java 2 SDK that will make it even more useful and powerful.

A Quick Refresher

In order to understand the concepts introduced in this article, it will be worthwhile to spend some time in reviewing the basics of exception management.

  • All Exceptions are derived from the Throwable class, which branches into Error and Exception. Errors are serious internal problems the JVM encounters and applications should not try to catch them. Whereas Exceptions are situations that an application might like to handle and may even recover from.
  • When a program violates the semantic constraints of the Java language or even the stated semantics of an application, an Exception is thrown by the JVM or application code. Throwing of an exception constitutes the creation of an object of type Throwable (or one of its subclasses) at the point where the abnormal condition is encountered and the control is transferred to the handling code. If no handler is found for the type of Exception thrown then the method uncaughtException(Thread, Throwable) is called on the ThreadGroup instance that is the parent of the current thread, the thread terminates, and printStackTrace() is called on the Throwable instance.
  • The exception handling constructs of Java are the try..catch..finally blocks. Code that can potentially throw an exception is wrapped in a try{} block; handling of exception by name is provided in one of the catch blocks. The finally{} block is used for some clean-up even when no exception is encountered. Code in finally{} is executed unconditionally. Strictly speaking this is not an exception handler like the catch{} block.
  • If an Exception is thrown from within a synchronized block or a synchronized method, then it is guaranteed that the locks held by the thread are relinquished.
  • Exceptions can be ‘checked’ or ‘unchecked’. All Errors and all Subclasses of the RuntimeException class belong to the ‘unchecked’ variety, which means that either they are program bugs (RuntimeException) or they are conditions beyond a programmer’s control. In either case, the application may not have an Exception handler for ‘unchecked’ exception. All other exceptions are ‘checked’ exceptions, which means that the application should either provide a handler for them or declare them in the throws clause of the method signature.
  • A catch (SomeException se){} is not only able to handle SomeException but also any subclass of SomeException.
  • An application can define its own exceptions. These can be derived from any of the Exception classes, and the application can throw the exception by using the throw statement.
  • As we know that each thread in Java has its own stack, when a Throwable is created as a result of an exception and we invoke printStackTrace() on the Throwable, the entire execution trace of the thread is printed on the standard error.

To Handle or to Throw Is the Question

If you have written any serious Java programs, then chances are that you have used the general Layer pattern more often than not. As the name suggests, the application is logically divided into layers where each layer has a function of its own. And each layer ‘talks’ to its adjacent layers through well-defined, published interfaces. Consider an example of an application requiring some persistence for some of its data and invoking the persist() method on a layer that handles persistence. The persistence-handling layer may write the data in a file or to a database or even in memory, which is decided by some configurable parameter of that layer. The application is not bothered by these details; however, it is interested in the outcome. If the data could not be persisted, then the application may take some action like notifying someone of the failure, retrying, or even trying some alternate action.

As we have stated, persistence could take place at a number of places and many things could go wrong. For example, if writing to a file, some IOException may be thrown; or if writing to a database, some SQLException may be thrown; or if writing to some buffers, some user-defined BufferCapacityReachedException may be thrown. In a good design, it is expected that the persistence layer will handle all these exceptions and map them to some higher-level exception that is intelligible to the application using the layer. In this case, all these exceptions could map into UnableToPersistException and be re-thrown for the application to handle.



…..
public void persist() throws UnableToPersistException {

try {
…….
// try database operation or file IO or writing to memory depending upon some flags
}
catch ( IOException ioe) {
throw new UnableToPersistException (“Some problem occurred in writing to file:” + ioe.getMessage());
}
catch (SQLException sqe) {
throw new UnableToPersistException (“Some problem occurred in writing to database:” + ioe.getMessage());
}
catch (BufferCapacityReachedException bcre) {
throw new UnableToPersistException (“Some problem occurred in writing to memory:” + ioe.getMessage());
}
}


What we have done here is shielded the upper layer from implementation details of the lower layer and provided a consistent and meaningful API to it. This also allows for extensibility, as in future the lower layer may write to a network socket in addition to the above and still throw a universal UnableToPersistException in case of any problem.
All this discussion is fine and is in accordance with the spirit of object-oriented design, but what if the higher layer were interested in the details of what caused the UnableToPersistException?

For example, what if we were logging for errors and the developer wanted to know the real reason for the exception? Unfortunately, in versions prior to JDK 1.4, the original cause was lost. Sure, we are passing the message when we are throwing our UnableToPersistException, but the fact that there was an IOException or the SQLException is lost forever.

Exception Chaining in JDK 1.4

Two new constructors have been added to class Throwable: Throwable(Throwable) Throwable(String, Throwable). As is apparent, the purpose of these constructors is to “wrap in” the cause of this exception. This “wrapped” exception can be reached by calling the getCause() method on any Throwable.

In order to have backward compatibility, a new method has been added — initCause(Throwable) — which provides the Throwable with the cause.
The printStackTrace() has also been modified to print the entire backtrace of the exception in question. It makes use of the getCause() method to recursively get hold of the causes wrapped in exceptions.

A new method, StackTraceElement [] getStackTrace(), provides access to the StackTraceElement object. This new object represents a single stack frame element. This has made the programmatic access to the stack frames possible. Some interesting methods available on the StackTraceElement object are getClassName(), getFileName(), getLineNumber(), getMethodName(). Using these, the program can access the place where this Throwable or others in the stack trace were created.

For example if you want to print the line number during the execution at any point of time you can use:

 
..........
StackTraceElement ste[] = (new Throwable()).getStackTrace();
    System.out.println(ste[0].getLineNumber());

The code snippet of the persist() method above can be modified easily to make use of the new Chained Exceptions facility:

.....
public void persist() throws UnableToPersistException  {

  try {
    .......
    // try database operation or file IO or writing to memory depending upon some flags
  }  
  catch ( IOException ioe) {
    throw new UnableToPersistException ("Some problem occurred in writing to file.", ioe);
  }
  catch (SQLException sqe) {
     throw new UnableToPersistException ("Some problem occurred in writing to database." ,sqe);                                                     
  }
  catch (BufferCapacityReachedException bcre) {
     throw new UnableToPersistException ("Some problem occurred in writing to memory ." ,bcre);
   }
}

Here, each instance of UnableToPersistException has a “wrapped in” cause, which can be accessed by the getCause() method.

Conclusion

Exceptions and exception handling are essential to good object-oriented design. They not only separate the error-handling code from the main execution but also provide a layer of abstraction for underlying errors to be intelligible to applications. With the new Exception Chaining facility and some new APIs, the Java Exception framework has provided the programmer with the missing link between lower layer and higher layer exceptions. For more details and API discussions, visit the following links.

About the Author

Nasir Khan is a Sun Certified Java programmer, with a B.E. in electrical engineering and a masters degree in systems. His areas of interest include Java applications, EJB, RMI, CORBA, JDBC and JFC. He is presently working with BayPackets Inc. in high-technology areas of telecom software.

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories