This was a day at work like any other. I was refactoring code and suddenly noticed that at one point we were too optimistic about the code and there is NullPointerException waiting to happen. But based on this object we had to return some data, soooo… let’s throw an exception?

And the debate began! What to do now? Return null? Throw exception? Which type of exception? Or maybe return Optional? Is there any other way?

I was thinking about it a lot. I asked a few people that are knowledgeable - there must be some rule, or at least rule of thumb. Oh, how disappointed I was.

Let’s start with option 1 - checked exception. Not a fan.

Their main purpose is to make sure that programmer is aware of what can go wrong with given operation. It forces programmer to consciously do something when bad things happen and recover from such situation. But often it comes down to logging error message and/or rethrowing exception, because it’s all we can reasonably do.

The big problem with checked exceptions is that you can’t ignore them. They can’t be invisible at some level, they ‘pollute’ each method that has anything to do with them. Even if you want it to be handled somewhere above, you need to declare this intention as throws CheckedException. It really sucks and makes code tightly coupled. If you want to change this exception or get rid of it, you need to change whole chain of calling methods that given exception is passing through. As methods should be short and you might not know how to handle exception straightaway, that will mean change in at least several places. Idea behind checked exceptions is noble, but I’m more in favor of flexibility than forcing developers to not do potentially stupid things. Especially as this brings tightly coupling.

Also if you are creating library or some kind of API, changing checked exception is a breaking change - client code will not compile with newer version. That’s bad. Someone might say that it should not compile, because there is new exception being thrown. Fair point. But as Anders Hejlsberg - architect of C# - rightly points out, most people just don’t care. Either they have no solution for new exception or they catch all exceptions and ignore them in some way or they really simply don’t care. Anders Hejlsberg even wrote:

In a well-written application there’s a ratio of ten to one, in my opinion, of try finally to try catch 1

To be fair, James Gosling - architect of Java - disagrees with him and he also makes some good points, but mostly in terms of software that should never ever fail.

Another common argument against checked exceptions is that at higher level you will eventually have many exceptions which is more clutter. I find this argument mostly invalid - with so many exceptions you would probably want some common exception class (so one exception only) or handle at least some of them at lower levels.

But nail to the coffin is how checked exceptions don’t play nice with lambdas. Lambdas can’t throw checked exceptions by default, so you need to handle an exception immediately or throw unchecked exception. Or do some workarounds like transforming exceptions automatically or compilation magic.

If that is not enough, even author of ‘Thinking in Java’ is not found of them - he even suggests runtime exception which wraps checked exception. While authority is not an argument, that means something - you should have good argument against it.

Let’s end with option 2 - unchecked exception.

Well…

It's something...

Unchecked exceptions are different and in theory should be used for unrecoverable situations. Unrecoverable is not very precise, because hey, we never want our app to crash. But there are some situations which we just can’t predict or do anything about like NullPointerException. And we can’t catch everything, right? But some apps actually do that - they catch ALL exceptions and restart (possibly with the same view state). Of course it should be done only once to not get into infinite loop.

As for argument for unchecked exceptions - naturally everything bad about checked exceptions is advantage of unchecked. They can be thrown from lambdas, they can be propagated without mentioning them, they make your code loosely (or more loosely) coupled. Everything is great!

Apart from when it’s not. You need to remember to catch them. If API changes and throws new runtime exception or modifies existing one, you’re screwed. If you are catching this exception far from its source, you’re double screwed. Certainly it will not happen often, it may not happen at all. And you write tests for your code that save you from such situations, right? But you need to make conscious decision that something like this might happen.

TL;DR:

Checked exceptions:

  • force you to think about corner cases - no need to read docs
  • they are part of interface - change is always visible
  • every layer rethrowing exception must know about it
  • don’t play nice with lambdas

Unchecked exceptions:

  • can be changed freely - they don’t influence interface
  • invisible for layers that are rethrowing exception
  • yes, work well with lambdas
  • you might forget to put it in place where this exception should be handled
  • if exception is no longer thrown, you might never notice it
  • if there is new exception you might not know about it until it happens

So which is better? As always, it depends. In general, I prefer unchecked exceptions, because they don’t pollute clients of the method and play nice with lambdas. But sometimes you really want to say that this operation is dangerous and it can easily go wrong. And sometimes you write critical software that should never fail, eg. medicine, aircrafts.

Also I love piece of advice from Bill Venners:

In general, exceptions that indicate an improper use of a class should be unchecked 2

Good thing is exceptions are for exceptional situations so you might not need to make such decision very often.

Going by the simple rule of thumb - recoverable situation - in my case checked exception is the way to go - situation is predictable and recoverable, right? But on the other hand do we really want to pass this nasty checked exception through so many layers? Or maybe there is some other way?! Stay tuned!

TELL ME NOW

References and/or stuff worth reading: