Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

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.

Read the whole series on exception handling:



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:

1 | 2 | 3 | 4 |  Next >
Resources