Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Understanding Java Exceptions


Java Solutions April 2001/import java.*

import java.*: Understanding Java Exceptions

Chuck Allison

Compared to C++, Java is fairly strict when it comes to exceptions. You may even have to “prove” to the compiler that you know how to handle them.


Although I’ve mentioned exceptions in this column before, it’s time to take a deeper look. You just can’t be effective as a Java developer without understanding why exceptions are part of the language, and how to use them effectively.

Out of Obscurity

In traditional programming languages like C, you usually use return values from a function to indicate an error condition. This practice unfortunately requires you to generate and check return values through every function in a nested chain of function calls, filling your source code with clutter that obscures the logic of your algorithms. Using exceptions, on the other hand, lets you handle errors and still have clear, readable code. Suppose, for example, that in main I call a function f, which calls a function g, which finally calls a function h. Without exceptions (or some similar mechanism [1]), if something goes wrong in h, you have to pass a return code back to g, then back to f, and finally back to main. With exceptions, all you have to do is put the call to f in a try block and make provisions for handling the error immediately thereafter, in a catch block such as in the following code snippet.

// In main...
try
{
    f();
}
catch (MyException e)
{
    System.out.println(e);
}

All you have to do to raise an exception in h is to throw it, like this:

// In h...
throw new
  MyException("optional text here");

The functions f and g don’t need to act as middlemen to propagate error-handling information up the call stack, as they would in C. That’s what I mean by minimizing code clutter.

Source code clutter of course doesn’t affect an end user, who couldn’t care less whether you write pretty code or not. He who ultimately pays your salary wants software that works and that doesn’t go ballistic should something unusual occur at run time. Here again, exceptions save the day because exceptions don’t like to be ignored. Java is so fastidious about how you use exceptions, in fact, that you can’t even compile a program successfully without proving that you know how to handle most of the exceptions that may raise their ugly heads when your program runs. But before we look at how to handle exceptions, let’s talk about where they come from.

Whence Exceptions?

One source of exceptions, of course, is when a throw statement executes; either in your own code or in code you call from other classes. A method should throw an exception whenever it is unable to fulfill its specification, not as an alternate way of returning normally from a function. The method Arrays.binarySearch, for example, returns the zero-based index of the location where it finds the value you’ve requested. If your search value is not in the array, it returns a very useful negative number (the opposite of where it should be plus one). It would be foolish to throw an exception in this case, because not finding a value in an array is normal behavior, and the return value is well defined. Exceptions are for the truly exceptional — things that aren’t normally supposed to happen.

As an example of when you should throw an exception, consider a stack data structure. The class in Listing 1 implements a fixed-size stack; you pass the stack’s capacity in a constructor argument. The contract of the pop method requires that the stack not be empty when it is called, so if you try to pop something from an empty stack, that’s exceptional. There is no meaningful value to return in this case, and you just plain shouldn’t have made the call in the first place. Similar logic applies to the top and push methods.

The only objects that can be thrown or caught by Java’s exception handling mechanism are instances of classes in the Throwable class hierarchy (see Figure 1). Most exceptions that application programmers create extend a class in the Exception sub-hierarchy (most often Exception itself), as StackException does in Listing 2. By convention, all exception classes should have two constructors: a no-arg constructor and one that takes a single String argument, which allows you to include useful additional information at the throw point.

Classes that extend Exception are known as checked exceptions because the compiler checks to see whether two things occur in a program using these classes:

  1. Every method that throws a checked exception must advertise it in the throws clause in its method definition, and
  2. Every method that calls a method that advertises a checked exception must either handle that exception (with try and catch) or must in turn advertise that exception in its own throws clause.

Because StackException is a checked exception I must include it in the throws clause of pop, top, and push. Furthermore, I need to convince the compiler that I know about these exceptions when I call these methods, as I did in Listing 3. In StackTest.doTest, each call to one of those three FixedStack methods is in a try block that can catch a StackException object. In the first try block, I force an exception by trying to add one too many objects to the stack. In the second try block, even though I’ve rigged things so that no StackException can occur, I still need the try and catch to make the compiler happy — because every checked exception must either be handled or passed up the line. I’m so confident that nothing can go wrong here that I’ll throw an InternalError, which aborts the program with an error message (more on this later).

Unchecked Exceptions

Just as checked exceptions are useful for signaling when your methods cannot fulfill their contract, there are other potential errors outside of your control, such as memory exhaustion, that can prevent the Java virtual machine from fulfilling its specification. Since you can’t plan for such errors ahead of time, you would have to catch them everywhere, which defeats the principle of maintaining uncluttered code. Therefore, these errors are unchecked exceptions, meaning exceptions that you don’t have to include in a throws clause. You are welcome to catch them (well, some of them), but the compiler won’t make you do it.

Unchecked exceptions fall into two categories: those that extend RuntimeException [2], and those that extend Error. I realize that I said earlier that classes inheriting from class Exception are checked exceptions, but that’s only half true: the whole truth is that classes in the Exception hierarchy other than those in the RuntimeException sub-hierarchy are checked exceptions.

Exceptions that extend RuntimeException represent errors that you may want to handle, although you’re not required to. The most common ones include:

  • ArithmeticException — when a divide by zero is attempted
  • ArrayStoreException — when you try to store an object in an array of incompatible type (usually occurs when using arrays of Object, since the compiler will catch any explicit type error)
  • IndexOutOfBoundsException — when an array index is out of range, for example
  • NegativeArraySizeException — obvious
  • NullPointerException — when you try to dereference a null object reference

As I stated above, RuntimeExceptions are not checked because making you advertise them would have no effect on establishing the correctness of your methods and would unnecessarily clutter your otherwise very readable code. Exceptions derived from the Error class, on the other hand, are unchecked because you never want to catch them! Error exceptions are severe errors that require shutting down the virtual machine. InternalError, which I used above, extends VirtualMachineError, which is an Error subclass. OutOfMemoryError is another obvious severe error, but there are others, like StackOverflowError, and various LinkageErrors. A linkage error means something has gone amiss when the class loader tried to load a class for execution, and commonly occurs either because some external source has introduced malicious code in an attempt to circumvent Java’s security mechanism, or the code came from an out-of-spec byte code generator.

Exceptions and Inheritance

When an exception occurs, the runtime mechanism searches back up the call stack for a catch block that matches the type of the exception. A “match” means any type for which the instanceOf operator applied to the exception returns true. In other words, catch (C c) will catch any exception that is of type C or a subclass of C. For this reason, if you want to catch all checked exceptions (not always an outlandish idea, really), you can simply catch an instance of Exception:

try
{
   ... whatever
}
catch (Exception e)
{
   // Catch all unchecked exceptions
   System.out.println(e);
}

When looking for a match, your catch blocks are inspected in the order in which they appear in the code, so be careful. If you try to place a handler for a superclass before a subclass handler; the compiler will “catch” your error, since the second block is unreachable.

Everyone knows that when you define application subclasses you need to be careful not break the contract of superclass methods by overriding them incorrectly in subclasses. Exceptions are part of a method’s contract, so always remember this simple rule:

  • Never add exception types to the throws clause of a method overridden in a subclass

This would break the contract of the superclass method and destroy any hope of substituting a subclass instance for a superclass instance (which, as the whole world knows by now, is the whole point of object-oriented programming). You can throw an instance of a subclass of the exception type however. For example, in the following, since AnotherException extends MyException, the contract is not violated.

class MyException extends Exception {}
class AnotherException extends MyException {}


class Parent {
   public void f() throws MyException {}
}

class Child extends Parent {
   public void
   f() throws AnotherException
   {}
}

class OverrideTest {
   public static void
   main(String[] args)
      throws AnotherException
   {
      Child c = new Child();
      c.f();
   }
}

You can also relax the specification by omitting an exception specification in the subclass method, as the following code illustrates.

class Parent
{
   public void
   f() throws Exception
   {}
}

class Child extends Parent
{
   public void f() // OK!
   {}
}

class Override
{
   public static void
   main(String[] args)
   {
      Child c = new Child();
      c.f();
   }
}

Since I didn’t add anything to the specification of Child.f, an instance of Child can still stand in for a Parent instance, and substitutability is preserved. From client code’s perspective, there is no harm putting a call to Child.f in a try block, even though an exception can’t occur.

All of the foregoing applies to interfaces as well.

Cleaning up after Yourself

Managing resources, such as files and network connections, requires a bit of care when exceptions can occur. Consider the following program that opens a file and passes the resulting FileReader to another method for processing.

import java.io.*;
class ProcessFile
{
   public static void
   main(String[] args)
   {
      if (args.length > 0)
      {
        FileReader f = null;
        try
        {
          // Open a file:
          f = new
            FileReader(args[0]);
          SomeOtherClass.process(f);
          f.close();
        }
        catch (IOException x)
        {
          System.out.println(x);
          if (f != null)
            f.close();
        }
      }
   }
}

If the FileReader constructor fails to open the file, then the IOException is caught, but f remains null, so no close attempt occurs. If the static method SomeOtherClass.process throws an IOException, then the exception is caught and f is closed in the exception handler. If no errors occur, then f is closed in the try block. There seems to be no way to avoid repeating the call to FileReader.close in the code, since we have to cover all possible paths. Since reallocating resources is something that must be done whether errors occur or not, there is a better way to do the job: the finally block. Code in a finally block executes no matter what. Using finally, only one call to close is needed:

import java.io.*;
class ProcessFile
{
   public static void
   main(String[] args)
   {
      if (args.length > 0)
      {
        FileReader f = null;
        try
        {
          // Open a file:
          f = new FileReader(args[0]);
          SomeOtherClass.process(f);
        }
        catch (IOException x)
        {
          System.out.println(x);
        }
        finally
        {
          if (f != null)
            f.close();
        }
      }
   }
}

The only thing that can prevent a finally block from executing is a call to System.exit, which shuts down the virtual machine. The general syntax for using exceptions, then, is to have a try block followed by zero or more catch blocks followed an optional finally block, but a try block cannot stand alone — it must have either at least one catch block, or a finally block, or both. The program in Listing 4 shows how tenacious finally is. When executed as-is, the output is

finally!
last statement

If you uncomment the line labeled with the numeral 1 in its comment, then a return from f occurs, but the finally block is still executed first, as evidenced by the output:

finally!

Now comment out line 1 again and uncomment line 2. There is no output this time since System.exit shuts downs the program entirely. With only line 3a uncommented, the exception thrown in the try block is caught in the adjacent catch, but it does nothing, so execution resumes after the catch, giving the same output as in the first instance:

finally!
last statement

Now for the hard question. What happens when both 3a and 3b are uncommented? You might think that the finally block executes, and then the exception thrown in the catch in f is caught by the catch in main. The compiler is smarter than that, though. It detects that the statement in f that prints "last statement" is unreachable and gives you a compile error. Gotcha!

The program in Listing 5 attempts to open a file for reading. Notice that the function f doesn’t handle any exceptions (hence its throws clause). It wouldn’t know what to do with an exception if it caught one. All f wants to do is make sure that the file is closed. By having a finally without a try, f can close the file and then let the exception pass up the stack to main. If you try to execute this program without a filename on the command line, the following is printed from main:

java.lang.ArrayIndexOutOfBoundsException: 0

since args is empty. If you give the name of a file that doesn’t exist, then you get this message instead:

java.io.FileNotFoundException: <file name>

If the file does exist, then you get the expected output:

file opened
file closed

It is conceivable that a catch block can partially handle an exception and yet still want to pass it up the line to an enclosing try block. In this case, all you need to do is re-throw the exception at the end of the catch block (e.g., throw x;).

Note the comment near the end of f that’s says “// beware lost exception!!!”. Since FileReader.close can also throw an exception, what do you think happens if it does? If it occurs in the midst of another exception, it becomes the current exception, so the previous exception is lost. Not much you can do about that.

Exceptions and Threads

If you’ve been in an unusually astute mood while reading this article, you may have noticed that thus far I’ve avoided a very important issue: what happens if an exception is not caught? The key to understanding the answer to that question is to pay particular attention to the execution path that the exception runtime mechanism takes. We say that it “goes back up the call stack.” The key is the word “stack.” Where does a stack reside (I don’t mean physically; I mean conceptually, as far as the JVM is concerned)? When the JVM is launched, it starts a thread for main and things progress from there. If any other threads start, they get their own stack. In other words, a stack belongs to a thread, and therefore an exception belongs to a thread. If the exception mechanism finds no matching handler, it falls off the end of its stack, and therefore the thread for that stack must terminate. So the short answer to the original question is: “Its thread dies.”

That sounds pretty serious, and it is, but what can we do if it happens? The thread is lost forever, but all threads belong to a thread group. Thread groups are collections of threads and can contain other thread groups. When a thread dies because of an uncaught exception, the runtime system calls the uncaughtException method in the dying thread’s thread group. The default behavior of ThreadGroup.uncaughtException is to call its parent thread group’s uncaughtExeption method. If a thread group has no parent, then it calls Throwable.printStackTrace for the dying exception, so at least some information is printed to System.err. This is all illustrated in Listing 6. ThreadTest.main launches an instance of MyThread, which prints a message and immediately throws an exception. There is no way that this exception can be caught in main, since it comes from a different thread, so the thread dies. Both the main thread and the instance of MyThread belong to the default thread group (since we didn’t cause it be otherwise), which has no parent, so the stack trace prints, but all the while the main thread is still running. I had main sleep so it could print its final message after the MyThread instance dies. If you want, you can override ThreadGroup.uncaughtException to do whatever you want, but I’ll leave that explanation for an article on threads.

Summary

  • Exceptions help separate error handling code from normal method logic, resulting in cleaner code.
  • Exceptions will not be ignored.
  • You should only throw an exception when a method can’t fulfill its specification.
  • All exception classes descend from java.lang.Throwable.
  • You should only throw checked exceptions (i.e., descended from java.lang.Exception).
  • Checked exceptions must either be handled or passed up the line by advertising them in a method’s throws clause.
  • The JVM will throw unchecked exceptions (i.e., descended from java.lang.RuntimeException or java.lang.Error) when it can’t fulfill its specification.
  • Don’t catch objects descended from java.lang.Error.
  • Don’t break a superclass method’s contract by adding to the throws clause in a subclass override.
  • Use finally to manage resources.
  • Beware of uncaught exceptions and be aware of ThreadGroup.uncaughtException.

Notes

[1] C offers the setjmp/longjmp mechanism, which allows you to jump from a deeply nested function to a point higher up in the execution stack without passing return codes up the stack manually. While this is fine for C, it is a big problem in object-oriented languages like C++ and Java because cleanup code that would execute under normal execution is skipped when you jump. Exceptions in both C++ and Java solve this problem. If, however, you just can’t ignore your curiosity to learn about setjmp and longjmp, see my article “Error Handling with C++ Exceptions, Part 1” in the November 1997 issue of CUJ.

[2] It is universally acknowledged that RuntimeException is a stupid name, since all exceptions are processed at run time. I haven’t heard a better alternative, though. In any case, it’s way too late now.

Chuck Allison is a long-time columnist with CUJ. During the day he does Internet-related development in Java and C++ as a Software Engineering Senior in the Custom Development Department at Novell, Inc. in Provo, Utah. He was a contributing member of the C++ standards committee for most of the 1990’s and authored the book C & C++ Codes Capsules: A Guide for Practitioners (Prentice-Hall, 1998). He has taught mathematics and computer science at six western colleges and universities and at many corporations throughout the U.S. You can email Chuck at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.