One of the discerning features of Java is its support for checked exceptions. Having a single mechanism for handling errors is wonderful, and using a separate channel (the exception handling mechanism) for moving the exceptions around works quite well. In practice however, it’s a slightly different story.
Endless wrapping Enterprise-level software architecture normally involves various layers from presentation to business logic and persistence layer. And keeping a clear separation between these layers is considered good practice. This also implies that exceptions crossing the boundaries between these layers are to be wrapped to hide implementation details of lower levels. This requires a lot of manual coding, or “plumbing” as I like to refer to it, and may end up in an endless series of catching and wrapping of exceptions.
Distracting It’s only obvious, that this plumbing is distracting you from the real task at hand, which is to implement your success scenario (and possibly some alternative scenarios). It’s easily becoming frustrating to accomplish this job when you’re constantly reminded of all the additional (failure) scenarios that possibly can happen. It becomes even more frustrating when they are of the “something went horribly wrong” variety and there’s little or nothing you can sensibly do when these exceptions are raised. Code only becomes unwieldy and hard to read.
Ignorable \Bruce Eckel came up with his own theory “(…) that when someone is trying to do something and you are constantly prodding them with annoyances, they will use the quickest device available to make those annoyances go away so they can get their thing done”. To proof his point, he demonstrated that he himself had done exactly that in the first edition of his book “Thinking in Java”:
...
} catch (SomeKindOfException e) {}
This actually proofs that checked exceptions easily can become counter-productive, because they trigger the wrong response by ignoring them in this way. It’s the intention that the compiler forces the programmer to either handle the exception or explicitly pass it on in a throws clause, to be taken care of properly at a later stage. But instead, they seem to encourage the behaviour to “swallow” exceptions. This can make debugging and maintenance of an application that much harder to do and clearly service no purpose other than to complicate matters.
Upside Checked exceptions are not bad in all cases, though. In at least two situations it makes perfect sense to use them, as Rod Waldhoff points out. “(…) For self-contained systems, where the distance between the thrower and catcher is small, or for ‘bottom tier’ subsystems, which act as a source for exceptions, but rarely as a sink or pipe (think of basic networking, file I/O, JDBC, etc.).
Checked exceptions are pretty much disastrous for the connecting parts of an application's architecture however. These middle-level APIs don't need or generally want to know about the specific types of failure that might occur at lower levels, and are too general purpose to be able to adequately respond to the exceptional conditions that might occur.”
Tunnelling A neat way to make " the best out of a worse situation", Bruce Eckel suggested the following approach. Instead of endlessly catching and wrapping of exceptions into a long chain, this tool converts any checked exception into RuntimeException while preserving all the information from the checked exception.
import java.io.*;
class ExceptionAdapter extends RuntimeException {
private final String stackTrace;
public Exception originalException;
public ExceptionAdapter(Exception e) {
super(e.toString());
originalException = e;
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
stackTrace = sw.toString();
}
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace (java.io.PrintStream s) {
synchronized(s) {
s.print(getClass().getName() + ": ");
s.print(stackTrace);
}
}
public void printStackTrace (java.io.PrintWriter s) {
All the original information, both the exception and its stack trace, is stored. If the exception gets all the way out to the console, it will behave like the original exception and print the stack trace using the usual printStackTrace(). However, you can also put a catch clause at a higher level in your program to catch an ExceptionAdapter and look for particular types of exceptions, like this:
catch(ExceptionAdapter ea) {
try {
ea.rethrow();
} catch(IllegalArgumentException e) {
// ...
} catch(FileNotFoundException e) {
// ...
}
// etc.
}
In other words, you're still able to catch the specific type of exception but you're not forced to put in all the exception specifications and try-catch clauses everywhere between the origin of the exception and the place that it's caught.