Catching an exception

Bruce Eckel’s Thinking in Java Contents | Prev | Next

If
a method throws an exception, it must assume that exception is caught and dealt
with. One of the advantages of Java exception handling is that it allows you to
concentrate on the problem you’re trying to solve in one place, and then
deal with the errors from that code in another place.

The
try block

try {
  // Code that might generate exceptions
}

Exception
handlers

try {
  // Code that might generate exceptions
} catch(Type1 id1) {
  // Handle exceptions of Type1
} catch(Type2 id2) {
  // Handle exceptions of Type2
} catch(Type3 id3) {
  // Handle exceptions of Type3
}
 
// etc... 

Each
catch clause (exception handler) is like a little method that takes one and
only one argument of a particular type. The identifier (
id1,
id2,
and so on) can be used inside the handler, just like a method argument.
Sometimes you never use the identifier because the type of the exception gives
you enough information to deal with the exception, but the identifier must
still be there.

The
handlers must appear directly after the try block. If an exception is thrown,
the exception-handling mechanism goes hunting for the first handler with an
argument that matches the type of the exception. Then it enters that catch
clause, and the exception is considered handled. (The search for handlers stops
once the catch clause is finished.) Only the matching catch clause executes;
it’s not like a
switch
statement in which you need a
break
after each
case
to prevent the remaining ones from executing.

Note
that, within the try block, a number of different method calls might generate
the same exception, but you need only one handler.


Termination
vs. resumption

There
are two basic models in exception-handling theory. In
termination
(which is what Java and C++ support), you assume the error is so critical
there’s no way to get back to where the exception occurred. Whoever threw
the exception decided that there was no way to salvage the situation, and they
don’t
want
to come back.

The
alternative is called
resumption.
It means that the exception handler is expected to do something to rectify the
situation, and then the faulting method is retried, presuming success the
second time. If you want resumption, it means you still hope to continue
execution after the exception is handled. In this case, your exception is more
like a method call – which is how you should set up situations in Java in
which you want resumption-like behavior. (That is, don’t throw an
exception; call a method that fixes the problem.) Alternatively, place your
try
block inside a
while
loop that keeps reentering the
try
block until the result is satisfactory.

The
exception specification

In
Java, you’re required to inform the client programmer, who calls your
method, of the exceptions that might be thrown from your method. This is
civilized because the caller can know exactly what code to write to catch all
potential exceptions. Of course, if source code is available, the client
programmer could hunt through and look for
throw
statements, but often a library doesn’t come with sources. To prevent
this from being a problem, Java provides syntax (and
forces
you
to use that syntax) to allow you to politely tell the client programmer what
exceptions this method throws, so the client programmer can handle them. This
is the
exception
specification

and it’s part of the method declaration, appearing after the argument list.

The
exception specification uses an additional keyword,
throws,
followed by a list of all the potential exception types. So your method
definition might look like this:

void
f() throws tooBig, tooSmall, divZero { //...

If
you say

void
f() { // ...

it
means that no exceptions are thrown from the method. (
Except
for
the exceptions of type
RuntimeException,
which can reasonably be thrown anywhere – this will be described later.)

There
is one place you can lie: you can claim to throw an exception that you
don’t. The compiler takes your word for it and forces the users of your
method to treat it as if it really does throw that exception. This has the
beneficial effect of being a placeholder for that exception, so you can
actually start throwing the exception later without requiring changes to
existing code.

Catching
any exception

It
is possible to create a handler that catches any type of exception. You do this
by catching the base-class exception type
Exception
(there are other types of base exceptions, but
Exception
is the base that’s pertinent to virtually all programming activities):

catch(Exception e) {
  System.out.println("caught an exception");
}

This
will catch any exception, so if you use it you’ll want to put it at the
end
of your list of handlers to avoid pre-empting any exception handlers that might
otherwise follow it.

Since
the
Exception
class is the base of all the exception classes that are important to the
programmer, you don’t get much specific information about the exception,
but you can call the methods that come from
its
base type Throwable:

String
getMessage( )

Gets
the detail message.

String
toString( )

Returns
a short description of the Throwable, including the detail message if there is
one.

The
first version prints to standard error, the second prints to a stream of your
choice. If you’re working under Windows, you can’t redirect
standard error so you might want to use the second version and send the results
to
System.out;
that way the output can be redirected any way you want.

//: ExceptionMethods.java
// Demonstrating the Exception Methods
package c09;
 
public class ExceptionMethods {
  public static void main(String[] args) {
    try {
      throw new Exception("Here's my Exception");
    } catch(Exception e) {
      System.out.println("Caught Exception");
      System.out.println(
        "e.getMessage(): " + e.getMessage());
      System.out.println(
        "e.toString(): " + e.toString());
      System.out.println("e.printStackTrace():");
      e.printStackTrace();
    }
  }
} ///:~ 

The
output for this program is:

Caught Exception
e.getMessage(): Here's my Exception
e.toString(): java.lang.Exception: Here's my Exception
e.printStackTrace():
java.lang.Exception: Here's my Exception
        at ExceptionMethods.main 

You
can see that the methods provide successively more information – each is
effectively a superset of the previous one.

Rethrowing
an exception

Sometimes
you’ll want to rethrow the exception that you just caught, particularly
when you use
Exception
to catch any exception. Since you already have the handle to the current
exception, you can simply re-throw that handle:

catch(Exception e) {
  System.out.println("An exception was thrown");
  throw e;
}

Rethrowing
an exception causes the exception to go to the exception handlers in the
next-higher context. Any further
catch
clauses for the same
try
block are still ignored. In addition, everything about the exception object is
preserved, so the handler at the higher context that catches the specific
exception type can extract all the information from that object.

//: Rethrowing.java
// Demonstrating fillInStackTrace()
 
public class Rethrowing {
  public static void f() throws Exception {
    System.out.println(
      "originating the exception in f()");
    throw new Exception("thrown from f()");
  }
  public static void g() throws Throwable {
    try {
      f();
    } catch(Exception e) {
      System.out.println(
        "Inside g(), e.printStackTrace()");
      e.printStackTrace();
      throw e; // 17
      // throw e.fillInStackTrace(); // 18
    }
  }
  public static void
  main(String[] args) throws Throwable {
    try {
      g();
    } catch(Exception e) {
      System.out.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace();
    }
  }
} ///:~ 

The
important line numbers are marked inside of comments. With line 17 un-commented
(as shown), the output is:

originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)

So
the exception stack trace always remembers its true point of origin, no matter
how many times it gets rethrown.

With
line 17 commented and line 18 un-commented,
fillInStackTrace( )
is used instead, and the result is:

originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.g(Rethrowing.java:18)
        at Rethrowing.main(Rethrowing.java:24)

Because
of
fillInStackTrace( ),
line 18 becomes the new point
of origin of the exception.

//: ThrowOut.java
public class ThrowOut {
  public static void
  main(String[] args) throws Throwable {
    try {
      throw new Throwable();
    } catch(Exception e) {
      System.out.println("Caught in main()");
    }
  }
} ///:~ 

It’s
also possible to rethrow a different exception from the one you caught. If you
do this, you get a similar effect as when you use
fillInStackTrace( ):
the information about the original site of the exception is lost, and what
you’re left with is the information pertaining to the new
throw:

//: RethrowNew.java
// Rethrow a different object from the one that
// was caught
 
public class RethrowNew {
  public static void f() throws Exception {
    System.out.println(
      "originating the exception in f()");
    throw new Exception("thrown from f()");
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(Exception e) {
      System.out.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace();
      throw new NullPointerException("from main");
    }
  }
} ///:~ 

The
output is:

originating the exception in f()
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at RethrowNew.f(RethrowNew.java:8)
        at RethrowNew.main(RethrowNew.java:13)
java.lang.NullPointerException: from main
        at RethrowNew.main(RethrowNew.java:18)

The
final exception knows only that it came from
main( ),
and not from
f( ).
Note that
Throwable
isn’t necessary in any of the exception specifications.

You
never have to worry about cleaning up the previous exception, or any exceptions
for that matter. They’re all heap-based objects created with
new,
so the garbage collector automatically cleans them all up.


[42]
This is a significant improvement over C++ exception handling, which
doesn’t catch violations of exception specifications until run time, when
it’s not very useful.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read