Apr 5, 2002 12:00 AM PT

Java 101: Exceptions to the programming rules, Part 2

Incorporate Java's throw-object/catch-object exception-handling technique into your Java programs

Last month's Java 101 article introduced you to exceptions. You learned that an exception diverges from normal execution and observed how C, C++, and Java programs handle exceptions. This month, I delve into Java's throw-object/catch-object exception-handling technique by focusing on that technique's exceptions class hierarchy, statements and clauses, and various rules. After completing this article, you will be able to incorporate Java's throw-object/catch-object exception-handling technique into your programs.

The exceptions class hierarchy

Java's throw-object/catch-object exception-handling technique includes an exceptions class hierarchy. Class Throwable resides at the top of that hierarchy, and the Exception and Error subclasses, which identify the hierarchy's program- and JVM-related exception class subhierarchies (respectively), reside immediately below -- as the figure below illustrates.

The exceptions class hierarchy organizes itself around three main classes

The exceptions class hierarchy associates with a StackTraceElement class, which lets you trace the method-call stack when an exception occurs. After exploring Throwable, Exception, and Error, I explore StackTraceElement and show how to extend the exceptions class hierarchy to suit your program's requirements.

The Throwable class

C++ lets any class represent an exception type. In contrast, Java permits only java.lang.Throwable and Throwable subclasses to represent those types. Apart from Object, an exception object's innermost layer is the Throwable layer. For that reason, you must familiarize yourself with the Throwable class.

Throwable possesses four constructors: Throwable(), Throwable(String message), Throwable(String message, Throwable cause), and Throwable(Throwable cause). The constructors reveal two facts about exception objects. First, you can associate a message with an exception object's Throwable layer. That message describes the exception's nature in human-readable terms, and a program can present that message to the program's user. Second, you can wrap one exception object inside another exception object. The wrapped exception object is called a cause because the wrapped object's presence causes an exception handler to create a second, different exception object that wraps itself around the wrapped object. A detailed examination of Throwable's constructors shows the following:

  • Throwable() initializes an exception object's Throwable layer to no message and no cause
  • Throwable(String message) initializes the Throwable layer to the contents of the String object that message references (and no cause)
  • Throwable(String message, Throwable cause) initializes the Throwable layer to the specified message and cause
  • Throwable(Throwable cause) initializes the Throwable layer's message to the cause's message (if there is a message) and the specified cause

Throwable also possesses eleven methods: getMessage(), getLocalizedMessage(), getCause(), initCause(Throwable cause), fillInStackTrace(), getStackTrace(), printStackTrace(), printStackTrace(PrintStream ps), printStackTrace(PrintWriter pw), setStackTrace(StackTraceElement [] stackTrace), and toString().

The getMessage() method returns a String reference to the Throwable layer's message. If that message does not exist, null returns. By default, getLocalizedMessage() calls getMessage() and passes that method's return value to whatever code calls getLocalizedMessage(). You can tailor getLocalizedMessage() to return the message in the user's written language by overriding that method in appropriate Throwable subclasses.

The getCause() method returns a reference to the Throwable layer's cause. In the case of no cause, getCause() returns null. The initCause(Throwable cause) method provides an alternate approach to initializing the cause. That method initializes the cause to the reference in that method's cause argument.

Tip
Not all legacy exception classes contain constructors that take cause arguments. If you need to store a cause in some exception object's Throwable layer and no constructor takes a cause argument, call that object's inherited initCause(Throwable cause) method. Remember, you can call that method only once. Furthermore, initCause(Throwable cause) throws a java.lang.IllegalArgumentException object if a reference to the calling object passes as an argument via cause. Finally, initCause(Throwable cause) throws a java.lang.IllegalStateException object if a call has already been made to that method, the Throwable(String message, Throwable cause) constructor, or the Throwable(Throwable cause) constructor.

Throwable dedicates six methods to working with a stack trace -- a sequence of method-call stack frames (where each stack frame describes an incomplete method call) and related source code information that identify the location of code responsible for throwing an exception object. fillInStackTrace() records the current stack trace in a private Throwable field. (Various Throwable methods call fillInStackTrace() behind the scenes.) The getStackTrace() method returns the contents of that field, which is an array of StackTraceElement objects. An exception handler can access that array and call StackTraceElement methods to help recover from an exception. (I discuss StackTraceElement's methods later in this article.) The three printStackTrace() methods print the stack-trace information in a suitable format on the standard error stream, a PrintStream stream, or a PrintWriter writer. (I discuss streams and writers in a future article.) Method setStackTrace(StackTraceElement[] stackTrace) lets you replace the current stack trace with a new stack trace, which stackTrace specifies.

Finally, Throwable overrides toString() to return a brief exception object description. You will see examples of toString() and other Throwable methods later in this article.

Exception and its children

Throwable's java.lang.Exception subclass serves as the root class of Throwable's program-related exception class subhierarchy. To describe an exception that originates from a program, such as a failed attempt to write to a file or an attempt to access a nonexistent array element, either the program or the JVM creates an object from a class in that subhierarchy. Examine the following list of exception class names and descriptions to familiarize yourself with the Exception subhierarchy's classes:

  • java.lang.CloneNotSupportedException: This class's objects describe attempts to call the clone() methods on objects whose classes do not directly (or indirectly, through superclasses) implement the Cloneable interface
  • java.io.FileNotFoundException: A java.io.IOException subclass, this class's objects describe failed attempts to open files
  • java.lang.InterruptedException: This class's objects describe interruptions to sleeping or waiting threads (which I'll discuss next month)
  • java.lang.ArrayIndexOutOfBoundsException: This class's objects describe attempts to access nonexistent array elements
  • java.lang.ArithmeticException: This class's objects describe attempts to divide integers by integer value zero
  • java.lang.NullPointerException: This class's objects describe attempts to access object fields or call object methods via object reference variables that contain null values

The previous list's first three classes represent different kinds of checked exceptions -- exceptions that a program must handle. To ensure the program handles the exceptions, the compiler checks the source code. In contrast, the list's last three classes represent different unchecked exceptions -- exceptions that a program doesn't need to handle. The compiler doesn't need to verify unchecked exception handling.

Note
Exception's java.lang.RuntimeException subclass and all RuntimeException subclasses identify different unchecked exception types. The JVM usually creates objects from those classes. All other Exception classes, including Exception, identify different checked exception types, and program code creates objects from those classes.

Programs must handle checked exceptions because programs can't guarantee such exceptions won't occur. How can a program guarantee it can write to a file?

The compiler does not require programs to handle unchecked exceptions for the following reasons:

  • Unchecked exceptions originate in many parts of a program; stating a program's intention to handle unchecked exception objects in all those places would overwhelm developers
  • Unless you build a library where someone else's program could possibly introduce a failure into your code, unchecked exceptions should never occur

Keep in mind that Exception subclass names end with the word Exception. That convention distinguishes Exception subclasses from Error subclasses.

Tip
Never create objects from the Exception class because an exception handler can't distinguish between the different exceptions those objects represent. (Operator instanceof proves useless in such situations.) To avoid that problem, always create exception objects from appropriate Exception subclasses (except for RuntimeException). The subclass's name tells you if that class is appropriate. For example, use FileNotFoundException to describe exceptions that arise from not finding files.

Error and its children

Throwable's java.lang.Error subclass serves as the root class of Throwable's JVM-related exception class subhierarchy. To describe an exception that originates from the JVM, such as the JVM running out of memory, the JVM creates an object from a class in that subhierarchy. JVM exceptions prove serious and often terminate the JVM.

I use the term error to distinguish JVM-related failure from program-related failure. Examples of error classes include java.lang.AssertionError, java.lang.ThreadDeath, and java.lang.VirtualMachineError. Not all error classes end with Error.

JVM errors belong to the unchecked exception category because a program can do little, if anything, to fix a JVM error. Also, don't create your own Error subclasses or try to handle JVM errors. The resulting program will probably prove unstable.

The StackTraceElement class

When your code must access stack-trace data (file names, source line numbers, method names, and so forth), that code can call Throwable's getStackTrace() method. The getStackTrace() method returns an array of references to java.lang.StackTraceElement objects -- where each object describes one stack frame in the stack trace.

Code can call StackTraceElement methods to access stack-trace data. Methods include getClassName(), getFileName(), getLineNumber(), getMethodName(), and isNativeMethod(). Each method returns information relevant to the stack frame associated with the StackTraceElement object.

What does stack-trace data look like? If you work with Sun Microsystems' SDK tools in a command-line environment, you probably see the stack-trace data produced by the printStackTrace() methods. If not, let's see if we can create some stack-trace data:

Listing 1. SortIntegerArray1.java

// SortIntegerArray1.java
class SortIntegerArray1
{
   public static void main (String [] args)
   {
      int numbers [] = { 22, 13, 5, 76, 3 };
      sort (numbers);
      for (int i = 0; i < numbers.length; i++)
           System.out.println (numbers [i]);
   }
   static void sort (int [] x)
   {
      for (int i = 0; i < x.length-1; i++)
           for (int j = 0; j < x.length-i; j++)
                if (x [j] > x [j+1])
                {
                    int temp = x [j];
                    x [j] = x [j+1];
                    x [j+1] = temp;
                }
   }
}

SortIntegerArray1 attempts to sort (order) a small integer array's contents into ascending order. However, the source code features a problem. After compiling that code and running the resulting class file, you encounter the following stack-trace data:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
        at SortIntegerArray1.sort(SortIntegerArray1.java:19)
        at SortIntegerArray1.main(SortIntegerArray1.java:9)

The stack-trace data identifies ArrayIndexOutOfBoundsException as the thrown exception object. Because ArrayIndexOutOfBoundsException signifies an unchecked exception arising from flawed code, the JVM creates an object from that class (behind the scenes) and searches for an appropriate exception handler. Although the program can provide that exception handler and receive the exception object, it chooses not to do so. When a program doesn't receive an exception object, the JVM's default exception handler accepts that responsibility: it receives that object and calls printStackTrace() to print a stack trace -- which is what you see above.

The two lines that start with at refer to a pair of stack frames on the method-call stack. Each stack frame describes a method that has not completed its execution when an exception occurs. From the previous output, you see that neither main() nor sort() (which main() called) is complete. Furthermore, you see the corresponding source code lines where main() called sort() and where sort()'s attempt to access an invalid array element led to the exception.

Now that you know something about StackTraceElement, that class's methods, and what stack-trace data looks like, you probably want to experiment with that class. But first, you need to understand a few more concepts. After presenting those concepts, I give you a chance to work with StackTraceElement.

Extend the exceptions class hierarchy

The exceptions class hierarchy includes a rich assortment of exception classes. When you need an exception class, you should first search that hierarchy for an appropriate class. If you cannot find an appropriate class or if you need a class resembling an existing class, you extend that hierarchy with a new class. Where does the new class fit within the overall class hierarchy? You must ask yourself if the class describes a checked exception or an unchecked exception. Assuming the answer is checked exception, the class appears within the Exception subhierarchy -- but not within that subhierarchy's RuntimeException portion. However, if the answer is unchecked exception, the class appears within the RuntimeException subhierarchy. Because you shouldn't handle unchecked exceptions that describe JVM failures, ignore the Error subhierarchy. To clarify this discussion, I present the following two examples:

  • You design an email library. That design reveals a pair of static methods that send and receive email messages. The send method uses Simple Mail Transfer Protocol (SMTP) to send an email message by engaging in a simple dialog of text-based commands and responses. Similarly, the receive method uses Post Office Protocol 3 (POP3) to receive an email message, using the same command/response-oriented dialog. Responses indicate success or failure. To describe failure, you create a pair of exception classes, one that describes SMTP failures and one that describes POP3 failures. Because responses -- from the SMTP and POP3 programs that communicate with the send/receive methods -- include response codes with human-readable messages, you store the message in your exception classes' Throwable layer and record the integer-based response code in a private field of each class. You avoid duplicating response code fields by introducing a generic superclass that declares that field and extendes that class with POP3 and SMTP exception subclasses. The following code fragment demonstrates:

    class EmailException extends Exception
    {
       private int responseCode;
       EmailMessage (int responseCode, String message)
       {
          // Call Exception(String message), which calls Throwable(String message).
          super (message);
          this.responseCode = responseCode;
       }
       int getResponseCode ()
       {
          return responseCode;
       }
    }
    class POP3Exception extends EmailException
    {
    }
    class SMTPException extends EmailException
    {
    }
    

    EmailException extends Exception because sometimes SMTP- and POP3-related exceptions arise from reasons other than flawed code -- perhaps the SMTP or POP3 program stops running during a communication session. As a result, POP3Exception and SMTPException identify different checked exceptions. (I'll discuss email, POP3, and SMTP in a future article.)

  • Suppose you design a mathematics library that alerts programs to overflow and underflow exceptions. To represent those exceptions, you introduce Overflow and Underflow classes. Because overflow and underflow exceptions indicate flawed code, they represent unchecked exceptions. That implies Overflow and Underflow subclass RuntimeException, as the following code fragment demonstrates:

    class Overflow extends RuntimeException
    {
    }
    class Underflow extends RuntimeException
    {
    }
    

    Because Overflow and Underflow lack duplicate code, you neither need to introduce a new class that extends RuntimeException nor have Overflow and Underflow subclass that new class.

Throw exception objects

The previous section referred to throwing an exception object. What does that phrase mean? For an answer, consider this: When the JVM detects an unchecked exception, the JVM creates and initializes an exception object. In contrast, when a program or library detects either a checked or unchecked exception, the program/library code creates and initializes an exception object and uses the throw statement to pass that object to the JVM. The throw statement has the following syntax:

'throw' expression ';'

The throw statement begins with keyword throw, continues with an expression that evaluates to an object reference, and terminates with a semicolon character. expression's type must be Throwable or a subclass type. Otherwise, the compiler reports an error. The following code fragment demonstrates creating, initializing, and throwing an exception object of type FileNotFoundException:

throw new FileNotFoundException ("Unable to open file " + filename); // Assume previous String filename; declaration. 

Programs typically use

throw

to throw checked exception objects, as the previous code fragment demonstrates. In contrast, libraries often use

throw

to throw unchecked exception objects. Library methods throw unchecked exception objects because guaranteeing that a calling method will always pass valid argument values to those methods proves impossible. For example, consider the following code fragment:

static void printReport (Employee e)
{
   if (e == null)
       throw new NullPointerException ();
   // ... other code ...
}

The printReport(Employee e) library method checks Employee argument e's contents. If e contains a null reference, printReport(Employee e) creates and throws a NullPointerException unchecked exception object. Execution immediately leaves, and does not return to, the printReport(Employee e) method.

Caution
Do not attempt to throw objects from any class apart from Throwable or a Throwable subclass. Otherwise, the compiler reports an error. The compiler also reports an error when a method attempts to throw a checked exception object but does not list that object's class name in the method's throws clause.

When a method throws a checked exception object, the compiler forces that method to list the object's class or superclass in the method's throws clause. That clause appends to a method's signature and has the following syntax:

'throws' exceptionIdentifier1 ',' exceptionIdentifier2 ',' ...

The throws clause begins with keyword throws and continues with a comma-delimited list of exceptionIdentifier's. Each exceptionIdentifier identifies the class of a checked exception object directly (or indirectly, through a called method) thrown from the method. The following code fragment illustrates a throws clause:

public static void main (String [] args) throws java.net.MalformedURLException
{
   if (args.length < 1) return;
   java.net.URL url = new java.net.URL (args [0]);
}

The above code fragment attempts to create an object from Java's URL class -- found in package java.net. If the contents of the String object, which args [0] references, do not represent a valid URL, the URL (String url) constructor throws a java.net.MalformedURLException object. Because that object represents a checked exception and because the main() method chooses not to receive that object, a throws clause must append to main()'s signature -- to inform the JVM that main() did not receive that object and that the JVM should begin searching for an appropriate exception handler with main()'s caller. (I explore URLs in a future article.)

Tip
To deal with failure that occurs inside a constructor, throw an exception object from that constructor. If the exception object is checked, append a throws clause to the constructor signature. For example: class Employee { Employee () throws IOException { /* ... appropriate code ... */ } }. When a constructor throws an exception object, the JVM does not create an object from the constructor's class. Using the previous example, if Employee() throws an IOException object, the JVM does not create an Employee object.

Throws clauses and method overriding

In "Object-Oriented Language Basics, Part 4," you learned about method overriding. In that article, you learned you must exactly specify a superclass method's name, return type, and parameter list in a subclass when overriding the superclass method. Because a throws clause is part of a method's signature, when overriding a superclass method that has a throws clause, keep in mind two factors:

  • When an exception class name appears in a subclass method's throws clause, either that name must also appear in the superclass method's throws clause or the name of the exception class's superclass must appear in the superclass method's throws clause.
  • An unchecked exception class name that appears in a subclass method's throws clause need not appear in the superclass method's throws clause. The compiler ignores unchecked exception class names when examining throws clauses.

To understand throws clauses and method overriding, consider the following code fragment:

import java.io.*;
class Parent
{
   void foo () throws IOException
   {
   }
}
class Child extends Parent
{
   void foo () throws EOFException, ArrayIndexOutOfBoundsException,
                      ClassNotFoundException
   {
   }
}

According to the code fragment, Child overrides Parent's foo() method. Notice both classes' throws clauses. Parent's throws clause lists one exception class (IOException), whereas Child's clause lists three: java.io.EOFException, ArrayIndexOutOfBoundsException, and java.lang.ClassNotFoundException.

Why won't the code fragment compile? Might the problem involve ArrayIndexOutOfBoundsException? The answer is no: the compiler ignores ArrayIndexOutOfBoundsException and other unchecked exception class names. Might the problem involve EOFException? Once again, the answer is no: class EOFException subclasses IOException, and you can legally list an exception subclass name in a subclass method's throws clause, provided the exception subclass's superclass name appears in the superclass method's throws clause. The problem lies with ClassNotFoundException; that checked exception class name does not appear in Parent's throws clause, and IOException is not ClassNotFoundException's superclass.

Catch exception objects

After the JVM acquires an exception object from either the JVM or a program/library, the JVM searches program/library code for a handler capable of dealing with those exceptions that the exception object's type represents. The search results in either a suitable program/library exception handler or the JVM's default exception handler. The JVM subsequently transfers the exception object and its execution to that exception handler. The acts of locating, calling, and passing the exception object to the exception handler are collectively known as catching an exception object.

The exception handler typically calls various exception object methods to learn more about the exception. Method return values assist the exception handler in its job -- although the default exception handler only displays those values so you can find out what went wrong and fix your code. See the earlier SortIntegerArray1 output for an example of what the default exception handler displays.

To represent an exception handler in source code, Java uses the catch clause. That clause has the following syntax:

'catch' '(' exceptionIdentifier objectIdentifier ')'
'{'
   statement ...
'}'

The catch clause begins with keyword catch and a parameter list consisting of one parameter. objectIdentifier names the parameter, and exceptionIdentifier serves as the parameter's type -- which must be Throwable or a subclass. The catch clause concludes with a block of exception-handling statements that allow a program/library to recover from an exception.

When an exception object is thrown, the JVM searches backwards through the method-call stack for the first catch clause whose exceptionIdentifier either exactly matches or is the supertype of the exception object's type. Upon finding a match, the JVM calls the catch clause and passes the exception object's reference to that clause, as part of the call. At that point, the exception object is caught. The following code fragment demonstrates a catch clause:

catch (FileNotFoundException exc)
{
   System.out.println (exc.getMessage ());
}

The code fragment's catch clause catches FileNotFoundException objects. When the JVM passes execution to catch (FileNotFoundException exc), that clause calls the exception object's getMessage() method to retrieve a String object that describes the exception. That object's contents subsequently print.

A catch clause cannot appear alone in source code. Instead, source code must express a catch clause in association with a try block. A try block has the following syntax:

'try'
'{'
   statement ...
'}'

The try block begins with keyword try and concludes with a statement block. Statements within that block can throw exception objects. The following code fragment throws a FileNotFoundException object from a try block and catches that object in the associated catch clause:

try
{
   throw new FileNotFoundException ();
}
catch (FileNotFoundException exc)
{
   System.out.println (exc.toString ()); // Display a string representation of the exception object.
}

Within the try block, a throw statement creates and throws a FileNotFoundException object to the JVM. In response, the JVM searches backwards through the method-call stack for a catch clause with a parameter of type FileNotFoundException. That search begins with the method that contains the throw statement's surrounding try block. Because an appropriate catch clause is part of the try block, the JVM transfers execution to that catch clause. Once the catch clause executes its last statement, execution continues with the first statement that follows that clause.

Caution
The catch keyword must appear immediately after a try block's closing brace character. The compiler reports an error upon encountering anything else ahead of catch.

The previous code fragment demonstrated catching a checked exception object; a program can also catch an unchecked exception object. For example, to quickly decipher Listing 1's problem, place SortIntegerArray1's sort (numbers); method call inside a try block and follow that statement with an appropriate catch clause. When the JVM throws an ArrayIndexOutOfBoundsException unchecked exception object, the new catch clause displays an appropriate message. Execution then leaves that clause and the program continues with the for loop statement. That loop statement and the subsequent System.out.println (numbers [i]); method-call statement cause the partially sorted array's contents to output -- which might clue you in to the code's problem. Listing 2's SortIntegerArray2 source code demonstrates this:

Listing 2. SortIntegerArray2.java

// SortIntegerArray2.java
class SortIntegerArray2
{
   public static void main (String [] args)
   {
      int numbers [] = { 22, 13, 5, 76, 3 };
      try
      {
         sort (numbers);
      }
      catch (ArrayIndexOutOfBoundsException e)
      {
         System.out.println ("Unable to sort: " + e.getMessage ());
      }
      for (int i = 0; i < numbers.length; i++)
           System.out.println (numbers [i]);
   }
   static void sort (int [] x)
   {
      for (int i = 0; i < x.length-1; i++)
           for (int j = 0; j < x.length-i; j++)
                if (x [j] > x [j+1])
                {
                    int temp = x [j];
                    x [j] = x [j+1];
                    x [j+1] = temp;
                }
   }
}

When run, SortIntegerArray2 produces the following output:

Unable to sort: null
13
5
22
3
76

The output proves that a program can use the catch clause to catch unchecked exception objects. The null that appears beside Unable to sort: indicates that the JVM did not specify a message as part of the ArrayIndexOutOfBoundsException object.

Multiple catch clauses

Associating multiple catch clauses with one try block, where each catch clause typically catches a specific exception object type can prove convenient. For example, one catch clause catches FileNotFoundException objects to handle that file exception type, whereas another catch clause catches other kinds of file-related exception objects to deal with other file-related exception types. The following code fragment demonstrates that scenario:

try
{
    // Open file.
    // File-manipulation statements.
    // Close file. This executes when no exceptions are thrown.
}
catch (FileNotFoundException e)
{
    // Handle exception.
    // Close file. This executes when file-not-found exceptions are thrown.
}
catch (IOException e)
{
    // Handle exception.
    // Close file. This executes when other file-related exceptions are thrown.
}

Should the try block directly/indirectly throw a FileNotFoundException object, the JVM transfers execution to the first catch clause. If some other input/output exception results in a different thrown IOException object, the second catch clause executes.

When the JVM confronts a sequence of multiple catch clauses, the JVM searches for the appropriate clause from the sequence's top to its bottom. Using the previous code fragment as an example, the JVM first examines catch (FileNotFoundException e). If it finds a match, that clause executes. Otherwise, the JVM examines catch (IOException e) and executes that clause if it matches. That search order implies an important point: some catch clauses might never execute, as the following code fragment demonstrates:

try
{
    // Open file.
    // File-manipulation statements.
    // Close file. This executes when no exceptions are thrown.
}
catch (IOException e)
{
    // Handle exception.
    // Close file. This executes when other file-related exceptions are thrown.
}
catch (FileNotFoundException e)
{
    // Handle exception.
    // Close file. This executes when file-not-found exceptions are thrown.
}

Suppose the previous code fragment's try block throws a FileNotFoundException object. Because FileNotFoundException is an IOException subclass, the JVM passes control to the catch (IOException e) catch clause. Not only does catch (IOException e) catch IOException objects, but that clause catches all IOException subclass objects. As a result, the catch (FileNotFoundException e) clause never executes. Because that clause is dead code, the compiler reports an error.

Caution
In a multiple catch clause sequence following a try block, if a catch clause that catches exception objects of a superclass exception type (such as IOException) precedes a clause that catches exception objects of one of that superclass exception type's subclass exception types (such as FileNotFoundException), the compiler reports an error.

Throw exception objects from catch clauses

Why would you throw an exception object from a catch clause? Consider what happens when a development team creates a Java library: To begin, the team leader designs a specification that itemizes those classes, methods, and fields that a program can access, including throws clauses and their checked exception class names. Think of the specification as a contract between the library and programs using that library. That contract specifies the responsibilities of the library and user programs to each other.

Given the specification, the team begins to code the library. Some library methods might call internal helper methods to assist in accomplishing various tasks. Those helper methods might throw various exception object types that the specification's throws clauses portion does not describe. Therefore, a library method must catch internal exception objects and throw different kinds of external exception objects that describe the exceptions according to the specification. Typically, translating internal exception objects to external exception objects occurs in a catch clause. Listing 3 demonstrates that translation activity:

Listing 3. TranslatedExceptionDemo1.java

// TranslatedExceptionDemo1.java
class TranslatedExceptionDemo1
{
   public static void main (String [] args)
   {
      try
      {
         Library.methodA (); // Line 9.
      }
      catch (Library.ExternalException e)
      {
         System.out.println (e.getMessage ());
         // Send stack trace to standard output device, instead of
         // default standard error device.
         e.printStackTrace (System.out);
      }
   }
}
class Library
{
   static void methodA () throws ExternalException
   {
      try
      {
         methodB ();
      }
      catch (InternalException e)
      {
         System.out.println (e.getMessage ());
         throw new ExternalException (); // Line 35.
      }
   }
   static class ExternalException extends Exception
   {
      ExternalException ()
      {
         super ("External Exception");
      }
   }
   private static void methodB () throws InternalException
   {
      throw new InternalException ();
   }
   private static class InternalException extends Exception
   {
      InternalException ()
      {
         super ("Internal Exception");
      }
   }
}

TranslatedExceptionDemo1's source code consists of top-level classes TranslatedExceptionDemo1 (to simulate a program accessing a library) and Library (to simulate a library). Library's architecture allows TranslatedExceptionDemo1 to access methodA() and the ExternalException nested top-level class. Library's architecture also reveals methodA()'s call to methodB(), which throws an InternalException object. methodA()'s catch clause catches that object, extracts the object's message, prints that message, creates an ExternalException object, and throws that object. From TranslatedExceptionDemo1's perspective, methodA() throws only ExternalException objects; TranslatedExceptionDemo1 is not aware of InternalException.

When run, TranslatedExceptionDemo1 produces the following output:

Internal Exception
External Exception
Library$ExternalException: External Exception
    at Library.methodA(TranslatedExceptionDemo1.java:35)
    at TranslatedExceptionDemo1.main(TranslatedExceptionDemo1.java:9)

The output's stack-trace portion results from the call to printStackTrace(PrintStream ps). The stack trace reveals the location of the code, in the Library class, that threw the ExternalException object. Furthermore, the stack trace reveals the location of the code, in TranslatedExceptionDemo1's main() method's try block, that initiated a call to Library's methodA() method -- which ultimately led to the exception. Finally, the stack trace displays method-call stack details, with the innermost method-call details at the top and the outermost method-call details at the bottom.

Instead of using a catch clause to translate an internal exception object to an external exception object and throw the external exception object, you can use a catch clause to rethrow a caught exception object. That approach allows some exception handling near an exception and additional exception handling in the catch clause of a method farther up the method-call stack; both catch clauses can access the same exception details. Because rethrowing the same exception object can violate a library specification, you should wrap the caught exception object inside a new exception object that conforms to the specification. To obtain the original caught exception object's details, a catch clause farther up the method-call stack can access the wrapped exception object from the new exception object. Wrapping an exception object inside another exception object is known as exception chaining. Examine Listing 4 for an exception chaining example:

Listing 4. TranslatedExceptionDemo2.java

// TranslatedExceptionDemo2.java
class TranslatedExceptionDemo2
{
   public static void main (String [] args)
   {
      try
      {
         Library.methodA (); // Line 9.
      }
      catch (Library.ExternalException e)
      {
         System.out.println (e.getMessage ());
         // Send stack trace to standard output device, instead of
         // default standard error device.
         e.printStackTrace (System.out);
      }
   }
}
class Library
{
   static void methodA () throws ExternalException
   {
      try
      {
         methodB (); // Line 29.
      }
      catch (InternalException e)
      {
         System.out.println (e.getMessage ());
         ExternalException ee = new ExternalException (); // Line 35.
         ee.initCause (e);
         throw ee;
      }
   }
   static class ExternalException extends Exception
   {
      ExternalException ()
      {
         super ("External Exception");
      }
   }
   private static void methodB () throws InternalException
   {
      throw new InternalException (); // Line 51.
   }
   private static class InternalException extends Exception
   {
      InternalException ()
      {
         super ("Internal Exception");
      }
   }
}

When run, TranslatedExceptionDemo2 produces the following output:

Internal Exception
External Exception
Library$ExternalException: External Exception
    at Library.methodA(TranslatedExceptionDemo2.java:35)
    at TranslatedExceptionDemo2.main(TranslatedExceptionDemo2.java:9)
Caused by: Library$InternalException: Internal Exception
    at Library.methodB(TranslatedExceptionDemo2.java:51)
    at Library.methodA(TranslatedExceptionDemo2.java:29)
    ... 1 more

The output begins with lines Internal Exception and External Exception. Those lines simply display the messages stored in the thrown InternalException and ExternalException objects. Following those lines, you see two stack traces. The stack trace beginning with Library$ExternalException refers to the method-call stack at the time the ExternalException object is thrown. Also, the stack trace beginning with Caused by refers to the method-call stack at the time the InternalException object is thrown. Notice the line that consists of ... 1 more. That line is shorthand notation. It refers to the previous stack trace's final line. If you were to remove ... 1 more and expand the output, you would observe the following:

Internal Exception
External Exception
Library$ExternalException: External Exception
    at Library.methodA(TranslatedExceptionDemo2.java:35)
    at TranslatedExceptionDemo2.main(TranslatedExceptionDemo2.java:9)
Caused by: Library$InternalException: Internal Exception
    at Library.methodB(TranslatedExceptionDemo2.java:51)
    at Library.methodA(TranslatedExceptionDemo2.java:29)
    at TranslatedExceptionDemo2.main(TranslatedExceptionDemo2.java:9)

The bottommost stack trace reveals that methodB() created and threw an InternalException object (line 51), that methodA()'s try block called methodB() (line 29), and that main()'s try block called methodA() (line 9). That stack trace also refers to itself as the cause of another thrown exception object, which the uppermost stack trace describes. The uppermost stack trace reveals that methodA() created an ExternalException object (line 35) and that main()'s try block called methodA() (line 9).

The chained exception output results from the ee.initCause (e); method call in methodA()'s catch clause. That method call is necessary because ExternalException does not provide access to an appropriate Throwable constructor. As an exercise, consider how to modify TranslatedExceptionDemo2 to produce the same output while eliminating the ee.initCause (e); method call.

Access stack-trace-element data from a catch clause

Earlier in this article, I promised you an example of the StackTraceElement class and its methods. I now fulfill that promise by introducing a program that uses StackTraceElement methods to access stack-trace-element data from a catch clause, and produce output similar to the previous TranslatedExceptionDemo2 output:

Listing 5. TranslatedExceptionDemo3.java

// TranslatedExceptionDemo3.java
class TranslatedExceptionDemo3
{
   public static void main (String [] args)
   {
      try
      {
         Library.methodA (); // Line 9.
      }
      catch (Library.ExternalException e)
      {
         System.out.println (e.getMessage ());
         System.out.println ("\nUppermost stack trace");
         StackTraceElement [] stearray = e.getStackTrace ();
         
         for (int i = 0; i < stearray.length; i++)
         {
              System.out.println ("\nStack Trace element: " + i);
              System.out.println ("File name = " +
                                  stearray [i].getFileName ());
              System.out.println ("Class name = " +
                                  stearray [i].getClassName ());
              System.out.println ("Method name = " +
                                  stearray [i].getMethodName ());
              System.out.println ("Line number = " +
                                  stearray [i].getLineNumber ());
         }
         System.out.println ("\nBottommost stack trace");
         stearray = e.getCause ().getStackTrace ();
         
         for (int i = 0; i < stearray.length; i++)
         {
              System.out.println ("\nStack Trace element: " + i);
              System.out.println ("File name = " +
                                  stearray [i].getFileName ());
              System.out.println ("Class name = " +
                                  stearray [i].getClassName ());
              System.out.println ("Method name = " +
                                  stearray [i].getMethodName ());
              System.out.println ("Line number = " +
                                  stearray [i].getLineNumber ());
         }
      }
   }
}
class Library
{
   static void methodA () throws ExternalException
   {
      try
      {
         methodB (); // Line 29.
      }
      catch (InternalException e)
      {
         System.out.println (e.getMessage ());
         ExternalException ee = new ExternalException (); // Line 35.
         ee.initCause (e);
         throw ee;
      }
   }
   static class ExternalException extends Exception
   {
      ExternalException ()
      {
         super ("External Exception");
      }
   }
   private static void methodB () throws InternalException
   {
      throw new InternalException (); // Line 51.
   }
   private static class InternalException extends Exception
   {
      InternalException ()
      {
         super ("Internal Exception");
      }
   }
}

When run, TranslatedExceptionDemo2 produces the following output:

Internal Exception
External Exception
Uppermost stack trace
Stack Trace element: 0
File name = TranslatedExceptionDemo3.java
Class name = Library
Method name = methodA
Line number = 72
Stack Trace element: 1
File name = TranslatedExceptionDemo3.java
Class name = TranslatedExceptionDemo3
Method name = main
Line number = 9
Bottommost stack trace
Stack Trace element: 0
File name = TranslatedExceptionDemo3.java
Class name = Library
Method name = methodB
Line number = 88
Stack Trace element: 1
File name = TranslatedExceptionDemo3.java
Class name = Library
Method name = methodA
Line number = 66
Stack Trace element: 2
File name = TranslatedExceptionDemo3.java
Class name = TranslatedExceptionDemo3
Method name = main
Line number = 9

Clean up

The try-block/catch-clause exception-handling arrangement features a problem. Suppose a statement within the try block opens a file. At some point, that file must close -- especially if the try-block/catch-clause executes in a loop. (If the file doesn't close, most likely, the program will eventually stop opening files because of limitations to the number of simultaneously open files.) To ensure each open file closes, a file-closing statement must appear within the try block and each catch clause associated with that block, as the following code fragment demonstrates:

try
{
    // open file.
    // file-manipulation statements.
    // close file. This executes when no exceptions are thrown.
}
catch (IOException e)
{
    // handle exception.
    // close file. This executes when file-related exceptions are thrown.
}

That duplicate file-closing code hearkens back to one of the problems with C's error code-testing approach to dealing with exceptions: redundancy. Ideally, the file-closing code should appear in only one place. Java supports that idea by providing a finally clause. That clause's syntax is:

'finally'
'{'
   // cleanup statements
'}'

A finally clause begins with the finally keyword, and a block of cleanup statements follows. If there are no catch clauses, the finally clause immediately follows a try block; otherwise it immediately follows the final catch clause. After the last statement executes, in either the try block (if that block did not throw any exception objects) or a catch clause (if an exception object was thrown), the finally clause executes. The following code fragment shows how finally eliminates redundancy:

try
{
    // Open file.
    // File-manipulation statements.
}
catch (IOException e)
{
    // Handle exception.
}
finally
{
    // Close file, whether or not an exception was thrown.
}

In the preceding code fragment, finally's file-closing code executes after the last statement executes in either the try block or the catch clause. That behavior implies the following: if there is no catch clause and an exception object is thrown from the try block, the finally clause executes, and then the JVM searches for an appropriate catch clause to catch that object.

Caution
The finally keyword must appear immediately after the closing brace character of a preceding try block or catch clause. The compiler reports an error upon encountering anything else ahead of finally.

A problem with finally clauses

The finally clause also features a problem -- lost exception objects. Suppose a method contains a try block with a finally clause, and the method signature includes a throws clause identifying the class name of the checked exception object that try throws. Now suppose code within the try block throws an exception object. Before the JVM can look for an appropriate exception handler, the finally clause must execute. Suppose code within the finally clause creates an exception object and throws that object. The first exception object disappears, and the JVM searches the method-call stack for a method with a catch clause that handles the new exception object. Listing 6 demonstrates that scenario:

Listing 6. MissingException.java

// MissingException.java
class ExceptionA extends Exception
{
   ExceptionA ()
   {
      super ("Exception A");
   }
}
class ExceptionB extends Exception
{
   ExceptionB ()
   {
      super ("Exception B");
   }
}
class MissingException
{
   public static void main (String [] args)
   {
      try
      {
         methodA ();
      }
      catch (ExceptionA e)
      {
         System.out.println (e.getMessage ());
      }
      catch (ExceptionB e)
      {
         System.out.println (e.getMessage ());
      }
   }
   static void methodA () throws ExceptionA, ExceptionB
   {
      try
      {
         methodB ();
      }
      finally
      {
         throw new ExceptionA ();
      }
   }
   static void methodB () throws ExceptionB
   {
      throw new ExceptionB ();
   }
}

When run, MissingException produces the following output:

Exception A

MissingException works as follows: main() invokes methodA(), which invokes methodB(). methodB() throws an exception object of type ExceptionB(). Before the JVM can locate a catch clause (in main()) that catches ExceptionB objects, the JVM must execute methodA's finally clause. However, that clause throws an ExceptionA object, and the JVM searches for an ExceptionA catch clause -- which the JVM finds in main(). The previous output proves that the ExceptionB object is missing.

Caution
Do not throw exception objects from finally clauses; you risk losing other exception objects.

Review

Java's throw-object/catch-object exception-handling technique consists of an exceptions class hierarchy, statements/clauses, and various rules. At the top of the exceptions class hierarchy, class Throwable describes an exception object's internal architecture: a message and a cause. Below Throwable, classes Exception and Error introduce subhierarchies that identify different kinds of program- and JVM-related exceptions, respectively. Apart from Exception's RuntimeException subclass hierarchy, all Exception classes identify checked exceptions. In contrast, RuntimeException and its subclasses (and Error and its subclasses) identify unchecked exceptions.

A method uses a throw statement to throw an exception object to the JVM. The calling method completes one of two tasks: It declares its interest in catching that object by wrapping the method call in a try block and specifying an associated catch clause. Or, it incorporates a throws clause as part of its signature to indicate that the calling method wants the throw statement's calling method to catch the exception object. If no method catches the exception object, the JVM passes that object to a default exception handler, which calls various Throwable methods to display exception object details and a stack trace. Whether or not a method chooses to catch an exception object, that method can incorporate a finally clause as part of a try block or as part of a try block and one or more catch clauses. Following the execution of a try block or a catch clause, the JVM passes execution to the finally clause. Code within finally performs various cleanup tasks.

To be successful with Java's exception-handling architecture, you must understand and follow the various rules I've outlined in this article.

Now turn to the study guide and do some homework. That homework will help solidify what you have just read.

I encourage you to email me with any questions you might have involving either this or any previous article's material. (Please keep such questions relevant to material discussed in this column's articles.) Your questions and my answers will appear in the relevant study guides.

In next month's article, you'll discover Java's thread support.

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners -- Java 2 By Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932) -- and helped write Special Edition Using Java 2 Platform (Que Publishing, 2001; ISBN: 0789724685). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.

Learn more about this topic