I know this is an old question but I've spent a while wrestling with checked exceptions and I've something to add. Please forgive me for the length of it!
My main beef with checked exceptions is that they ruin polymorphism. It's impossible to make them play nicely with polymorphic interfaces.
Take the good ol' Java List
interface. We have common in-memory implementations like ArrayList
and LinkedList
. We also have the the skeletal class AbstractList
which makes it easy to design new types of list. For a read-only list we need to implement only two methods: size()
and get(int index)
.
This example WidgetList
class reads some fixed-size objects of type Widget
(not shown) from a file:
class WidgetList extends AbstractList<Widget> {
private static final int SIZE_OF_WIDGET = 100;
private final RandomAccessFile file;
public WidgetList(RandomAccessFile file) {
this.file = file;
}
@Override
public int size() {
return (int)(file.length() / SIZE_OF_WIDGET);
}
@Override
public Widget get(int index) {
file.seek((long)index * SIZE_OF_WIDGET);
byte[] data = new byte[SIZE_OF_WIDGET];
file.read(data);
return new Widget(data);
}
}
By exposing the Widgets using the familiar List
interface, you can retrieve items (list.get(123)
) or iterate a list (for (Widget w : list) ...
) without needing to know about WidgetList
itself. One can pass this list to any standard methods that use generic lists, or wrap it in a Collections.synchronizedList
. Code that uses it need neither know nor care whether the "Widgets" are made up on the spot, come from an array, or are read from a file, or a database, or from across the network, or from a future subspace relay. It will still work correctly because the List
interface is correctly implemented.
Except it isn't. The above class doesn't compile because the file access methods may throw an IOException
, a checked exception which you have to "catch or specify". You can't specify it as thrown -- the compiler won't let you because that would violate the contract of the List
interface. And there is no useful way that WidgetList
itself can handle the exception (as I'll expound on later).
Apparently the only thing to do is catch and rethrow checked exceptions as some unchecked exception:
@Override
public int size() {
try {
return (int)(file.length() / SIZE_OF_WIDGET);
} catch (IOException e) {
throw new WidgetListException(e);
}
}
public static class WidgetListException extends RuntimeException {
public WidgetListException(Throwable cause) {
super(cause);
}
}
((Edit: Java 8 has added an UncheckedIOException
class for exactly this case: for catching and rethrowing IOException
s across polymorphic method boundaries. Kind of proves my point!))
So checked exceptions simply don't work in cases like this. You can't throw them. Ditto for a clever Map
backed by a database, or an implementation of java.util.Random
connected to a quantum entropy source via a COM port. As soon as you try to do anything novel with the implementation of a polymorphic interface, the concept of checked exceptions fails. But checked exceptions are so insidious that they still won't leave you in peace, because you still have to catch and rethrow any from lower-level methods, cluttering the code and cluttering the stack trace.
I find that the ubiquitous Runnable
interface is often backed into this corner, if it calls something which throws checked exceptions. It can't throw the exception as is, so all it can do is clutter the code by catching and rethrowing as a RuntimeException
.
Actually, you can throw undeclared checked exceptions if you resort to hacks. The JVM, at run time, doesn't care about checked exception rules, so we need to fool only the compiler. The easiest way to do this is to abuse generics. This is my method for it (class name shown because (before Java 8) it's required in the calling syntax for the generic method):
class Util {
/**
* Throws any {@link Throwable} without needing to declare it in the
* method's {@code throws} clause.
*
* <p>When calling, it is suggested to prepend this method by the
* {@code throw} keyword. This tells the compiler about the control flow,
* about reachable and unreachable code. (For example, you don't need to
* specify a method return value when throwing an exception.) To support
* this, this method has a return type of {@link RuntimeException},
* although it never returns anything.
*
* @param t the {@code Throwable} to throw
* @return nothing; this method never returns normally
* @throws Throwable that was provided to the method
* @throws NullPointerException if {@code t} is {@code null}
*/
public static RuntimeException sneakyThrow(Throwable t) {
return Util.<RuntimeException>sneakyThrow1(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> RuntimeException sneakyThrow1(
Throwable t) throws T {
throw (T)t;
}
}
Hurray! Using this we can throw a checked exception any depth up the stack without declaring it, without wrapping it in a RuntimeException
, and without cluttering the stack trace! Using the "WidgetList" example again:
@Override
public int size() {
try {
return (int)(file.length() / SIZE_OF_WIDGET);
} catch (IOException e) {
throw sneakyThrow(e);
}
}
Unfortunately, the final insult of checked exceptions is that the compiler refuses to allow you to catch a checked exception if, in its flawed opinion, it could not have been thrown. (Unchecked exceptions do not have this rule.) To catch the sneakily thrown exception we have to do this:
try {
...
} catch (Throwable t) { // catch everything
if (t instanceof IOException) {
// handle it
...
} else {
// didn't want to catch this one; let it go
throw t;
}
}
That is a bit awkward, but on the plus side, it is still slightly simpler than the code for extracting a checked exception that was wrapped in a RuntimeException
.
Happily, the throw t;
statement is legal here, even though the type of t
is checked, thanks to a rule added in Java 7 about rethrowing caught exceptions.
When checked exceptions meet polymorphism, the opposite case is also a problem: when a method is spec'd as potentially throwing a checked exception, but an overridden implementation doesn't. For example, the abstract class OutputStream
's write
methods all specify throws IOException
. ByteArrayOutputStream
is a subclass that writes to an in-memory array instead of a true I/O source. Its overridden write
methods cannot cause IOException
s, so they have no throws
clause, and you can call them without worrying about the catch-or-specify requirement.
Except not always. Suppose that Widget
has a method for saving it out to a stream:
public void writeTo(OutputStream out) throws IOException;
Declaring this method to accept a plain OutputStream
is the right thing to do, so it can be used polymorphically with all kinds of outputs: files, databases, the network, and so on. And in-memory arrays. With an in-memory array, however, there is a spurious requirement to handle an exception that can't actually happen:
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
someWidget.writeTo(out);
} catch (IOException e) {
// can't happen (although we shouldn't ignore it if it does)
throw new RuntimeException(e);
}
As usual, checked exceptions get in the way. If your variables are declared as a base type that has more open-ended exception requirements, you have to add handlers for those exceptions even if you know they won't occur in your application.
But wait, checked exceptions are actually so annoying, that they won't even let you do the reverse! Imagine you currently catch any IOException
thrown by write
calls on an OutputStream
, but you want to change the variable's declared type to a ByteArrayOutputStream
, the compiler will berate you for trying to catch a checked exception that it says cannot be thrown.
That rule causes some absurd problems. For example, one of the three write
methods of OutputStream
is not overridden by ByteArrayOutputStream
. Specifically, write(byte[] data)
is a convenience method that writes the full array by calling write(byte[] data, int offset, int length)
with an offset of 0 and the length of the array. ByteArrayOutputStream
overrides the three-argument method but inherits the one-argument convenience method as-is. The inherited method does exactly the right thing, but it includes an unwanted throws
clause. That was perhaps an oversight in the design of ByteArrayOutputStream
, but they can never fix it because it would break source compatibility with any code that does catch the exception -- the exception that has never, is never, and never will be thrown!
That rule is annoying during editing and debugging too. E.g., sometimes I'll comment out a method call temporarily, and if it could have thrown a checked exception, the compiler will now complain about the existence of the local try
and catch
blocks. So I have to comment those out too, and now when editing the code within, the IDE will indent to the wrong level because the {
and }
are commented out. Gah! It's a small complaint but it seems like the only thing checked exceptions ever do is cause trouble.
I'm nearly done. My final frustration with checked exceptions is that at most call sites, there's nothing useful you can do with them. Ideally when something goes wrong we'd have a competent application-specific handler that can inform the user of the problem and/or end or retry the operation as appropriate. Only a handler high up the stack can do this because it's the only one that knows the overall goal.
Instead we get the following idiom, which is rampant as a way to shut the compiler up:
try {
...
} catch (SomeStupidExceptionOmgWhoCares e) {
e.printStackTrace();
}
In a GUI or automated program the printed message won't be seen. Worse, it plows on with the rest of the code after the exception. Is the exception not actually an error? Then don't print it. Otherwise, something else is going to blow up in a moment, by which time the original exception object will be gone. This idiom is no better than BASIC's On Error Resume Next
or PHP's error_reporting(0);
.
Calling some kind of logger class is not much better:
try {
...
} catch (SomethingWeird e) {
logger.log(e);
}
That is just as lazy as e.printStackTrace();
and still plows on with code in an indeterminate state. Plus, the choice of a particular logging system or other handler is application-specific, so this hurts code reuse.
But wait! There is an easy and universal way to find the application-specific handler. It's higher up the call stack (or it is set as the Thread's uncaught exception handler). So in most places, all you need to do is throw the exception higher up the stack. E.g., throw e;
. Checked exceptions just get in the way.
I'm sure checked exceptions sounded like a good idea when the language was designed, but in practice I've found them to be all bother and no benefit.