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 reference to the current exception, you can simply rethrow that reference:
catch(Exception e) {
System.err.println("An exception was thrown");
throw e;
}
Rethrowing an exception causes it 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.
If you simply rethrow the current exception, the information that you print about that exception in printStackTrace( ) will pertain to the exception’s origin, not the place where you rethrow it. If you want to install new stack trace information, you can do so by calling fillInStackTrace( ), which returns a Throwable object that it creates by stuffing the current stack information into the old exception object. Here’s what it looks like:
//: c09:Rethrowing.java
// Demonstrating fillInStackTrace()
import com.bruceeckel.simpletest.*;
public class Rethrowing {
private static Test monitor = new Test();
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.err.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.err.println(
"Caught in main, e.printStackTrace()");
e.printStackTrace();
}
monitor.expect(new String[] {
"originating the exception in f()",
"Inside g(),e.printStackTrace()",
"java.lang.Exception: thrown from f()",
"%% \tat Rethrowing.f(.*?)",
"%% \tat Rethrowing.g(.*?)",
"%% \tat Rethrowing.main(.*?)",
"Caught in main, e.printStackTrace()",
"java.lang.Exception: thrown from f()",
"%% \tat Rethrowing.f(.*?)",
"%% \tat Rethrowing.g(.*?)",
"%% \tat Rethrowing.main(.*?)"
});
}
} ///:~
The important line numbers are marked as comments. With line 17 uncommented (as shown), the output is as shown, 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 uncommented, 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:9)
at Rethrowing.g(Rethrowing.java:12)
at Rethrowing.main(Rethrowing.java:23)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.g(Rethrowing.java:18)
at Rethrowing.main(Rethrowing.java:23)
(Plus additional complaints from the Test.expect( ) method.) Because of fillInStackTrace( ), line 18 becomes the new point of origin of the exception.
The class Throwable must appear in the exception specification for g( ) and main( ) because fillInStackTrace( ) produces a reference to a Throwable object. Since Throwable is a base class of Exception, it’s possible to get an object that’s a Throwable but not an Exception, so the handler for Exception in main( ) might miss it. To make sure everything is in order, the compiler forces an exception specification for Throwable. For example, the exception in the following program is not caught in main( ):
//: c09:ThrowOut.java
// {ThrowsException}
public class ThrowOut {
public static void
main(String[] args) throws Throwable {
try {
throw new Throwable();
} catch(Exception e) {
System.err.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:
//: c09:RethrowNew.java
// Rethrow a different object from the one that was caught.
// {ThrowsException}
import com.bruceeckel.simpletest.*;
class OneException extends Exception {
public OneException(String s) { super(s); }
}
class TwoException extends Exception {
public TwoException(String s) { super(s); }
}
public class RethrowNew {
private static Test monitor = new Test();
public static void f() throws OneException {
System.out.println("originating the exception in f()");
throw new OneException("thrown from f()");
}
public static void
main(String[] args) throws TwoException {
try {
f();
} catch(OneException e) {
System.err.println(
"Caught in main, e.printStackTrace()");
e.printStackTrace();
throw new TwoException("from main()");
}
monitor.expect(new String[] {
"originating the exception in f()",
"Caught in main, e.printStackTrace()",
"OneException: thrown from f()",
"\tat RethrowNew.f(RethrowNew.java:18)",
"\tat RethrowNew.main(RethrowNew.java:22)",
"Exception in thread \"main\" " +
"TwoException: from main()",
"\tat RethrowNew.main(RethrowNew.java:28)"
});
}
} ///:~
The final exception knows only that it came from main( ) and not from f( ).
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.