When I was at university learning Java, Exceptions seemed ubiquitous, it was simply the way to do error handling. It did lead to code with many throws in function declarations and try/catch blocks sprinkled all over. It felt a bit awkward, but I didnโ€™t question it too much.

Now many years later, I look at the concept of exceptions completely differently. I guess this is a general trend, given that Kotlin (what I consider to be modern Java) doesnโ€™t even support checked exceptions.

My opinion on exceptions is this:

Exceptions should be exceptional

What a shocker.

The appropriate times to throw exceptions

Since exceptions should be exceptional, I only see 2 valid cases to throw exceptions:

  • Inherently unreliable code (network/disk/โ€ฆ)
  • Unrecoverable cases

You could even argue against inherently unreliable code, but Java code is often implemented that way, so you will run into exceptions in those cases.

How to handle exceptions

There should also be some guidelines on how to handle the exceptional exceptions being thrown.

Exceptions that are thrown by inherently unreliable code

Exceptions from unreliable code should be handled as close to the source of the exception as possible. All other code should handle errors through its return type, for example, nullable types or an even more explicit concept, such as a dedicated class. The return type takes the role of what used to be the throws Exception keyword in function declarations.

Using return types for error handling will force you to do more checks on failure cases, but that doesnโ€™t necessarily bloat the code if languages have explicit language support for [safe calls as Kotlin does][4].

Also, using return types makes all potential code paths much more explicit, making it easier to reason about a large code base.

Exceptions that are thrown by unrecoverable cases

Unrecoverable cases are unrecoverable. In this case, the exception signals that the entire request should fail or the entire program should crash. There can be a single try/catch block at the bottom of the stack to map the exception to an error state but thatโ€™s it.

An example would be throwing a ForbiddenException when a user doesnโ€™t have appropriate permissions in an HTTP request, which can be transformed by the mapper to an HTTP 403 Forbidden.

Now of course, nobody is stopping a developer from catching that exception and executing an alternative code path, but that means exceptions are being used for control flow, which is a big no-no and should never pass code review.

Conclusion

There are of course always exceptions (no pun intended), but when following these rules, there shouldnโ€™t be any exception-handling in your business logic. This makes the code explicit and easy to reason about. In the rare cases where exceptions are needed anyway (although I canโ€™t think of any), making sure the behaviour is well-documented is the way to go.