Why Java's Checked Exceptions Are an Issue
Exceptions were originally introduced to make programs safer. Back in the days languages like C used return codes to signal whether the call was actually successful. The return codes could easily be ignored. So you end up with a program that just continues executing - even though an error occured. Exceptions are meant to solve this. To signal an error an exception is thrown. This exception has to be handled if the error should be resolved. Handling the exception is separated from the rest of the control flow e.g. in a try-block. So handling an error is separated from the rest of the program.
Java introduced three types of exceptions:
- Errors like OutOfMemoryError are unrecoverable problems. So your code cannot really handle them.
- Unchecked exceptions are subclasses of RuntimeException. While they can be handled it is not mandatory to do so. Examples include the NullPointerException. This exception can be thrown any time an object is accessed. So it makes no sense to force developers to handle it.
- All other exceptions are checked exceptions i.e. the compiler ensures that they are handled using catch or the method declares that it throws the exception.
Originally I thought an exception is an extension of the return type of a method. Instead of a value of the "normal" return type it throws or "returns" an exception. So if the program should be sound concerning types every type of exception needs to be handled - just as you would handle a normal return value. Obviously it makes sense to use the type checker to ensure this happens. So checked exceptions make a lot of sense.
Checked exceptions are also used throughout the Java APIs. The developers of these APIs obviously know what they are doing - so again checked exceptions again seemed like a good idea.
However, later on I did quite a few code reviews. Some typical ways to handle checked exceptions appeared:
- Exceptions were just ignored i.e. the catch block was empty.
- Exceptions are logged. This is also what the default Eclipse code snippet does. The code just swallows the exception. However, developers are supposed to actually handle the exceptions and not just log them. More often than not a closer examination revealed that the exception really was about a serious problem and the program should probably have signaled a failure to the user - and not just continue and log the problem somewhere. This is pretty much the same situation as ignoring return codes in C - which exceptions were supposed to solve.
- Sometime the exception is wrapped and rethrown. This is more or less identical to an unchecked exception. The exception is just propagated. Just the type changes, of course - but that might not justify the code written.
Only seldom the exception is really actually handled e.g. by trying an alternative algorithm or returning some kind of default value.
You could argue that this is just the fault of the developers who wrote the code. Why did they not implement proper exception handling?
However, some facts made me wonder whether this is actually the correct explanation:
- The Spring project only uses unchecked exceptions
- Even EJB introduces the EJBException that can be used to wrap other exception in an unchecked exception.
- There is hardly any other language using checked exceptions. Wikipedia lists OCaml, CLU and Modula-3. This makes Java the only main stream language using checked exceptions. This should really make you wonder - why didn't C# for example implement this feature, too?
So apparently checked exceptions might not be such a smart idea after all - otherwise everybody would be using them. And the reason why so much exception handling is implemented poorly might be that developers are forced to write code to handle exceptions - even if there is no sensible way to do so. This is the primary difference between checked and unchecked exceptions: Unchecked exceptions have a sensible default i.e. the exception is just propagated to the next level in the call hierarchy. Checked exceptions lack such default and must therefore be handled in one way or another.
So essentially checked exceptions force the developer of the calling code to handle the exception. This makes only sense if it can be handled sensible. In a lot of cases that is not possible. Therefore I believe checked exceptions should hardly be used. In almost any case it is better to rely on an unchecked exception. Then the default is that the exception is propagated if not handled - which is usually the better alternative. Note that it is still possible to handle the exception if needed.
I think relying more on unchecked exceptions is primarily a matter of courage. A lot of projects and libraries in the Java space use checked exceptions. They are without a doubt overused - see JDBC's SQLException
for example. It is a checked exception but can hardly ever be handled sensible. Not to follow these bad examples truly takes courage. Maybe introducing checked exception is even the greatest mistake in Java's language design.
Labels: Exceptions, Java