Reviving Checked exceptions

Hello everyone,

I would like to revive discussions about checked exceptions.

I last couple of years, Kotlin improved and grew, entered new platforms.
At the same time, Android also became quite popular in automotive industry.

I work at an agency, where we have multiple Android and iOS applications (Swift / Kotlin Multi Platform), as well as automotive apps (being done in Android/Kotlin)

I went through the old discussions about this topic, and mostly it is that “people tend to ignore errors”, “it gets more complicated as projects grow”, “might be too late”…

Kotlin website also mentions this topic Exceptions | Kotlin Documentation

I want to challenge these assumptions and ask you to reevaluate checked exceptions.

From our experience, not having possibility to know which function throws or not, is a huge issue.
There are multiple teams working on different modules, some of them at external company, some of them at another agency, anything anywhere could throw (just like before optional anything anywhere could be null).

From our crash reporting analysis, a crash that is caused due to uncaught throw (where some other module throws an error) is always in the top 3. It is easy to fix these crashes after they happen, however, we need to prevent them. We never had such crashes in our Java/Swift code, and this also slows down Kotlin adoption for some teams.

As Kotlin moves to new environments where safety(not crashing) is more critical, there should be some way to prevent these issues.

How can we resolve this?

What do you think, how is your experience with such crashes?

3 Likes

I tend to use exceptions for unrecoverable or unexpected errors only, so elaboration should fail, for expected exception sealed class works well.
I realized this solution using coroutine, it does not resolve all problems, but it sometimes helps.

I tried to summarise the issues in this StackOverflow answer — but as that says, I too have not been fully convinced of the benefit of making all exceptions unchecked.

The worst thing is that you not only have the option of ignoring exceptions, but now you can’t even tell what exceptions could be thrown, so you can’t handle them properly even if you want to! That seems to be throwing the baby out with the bathwater.

I’ve previously suggested a potential solution to this: Kotlin should do exception inference in much the same way that it does type inference: it should automatically detect which exception types could be thrown by a function, and infer a @Throws on the function if one is not declared.

All exceptions could still be treated as unchecked, but that would at least allow people to see what exceptions were possible, and handle them if they wanted. It would also interoperate better with Java.

(It should then be possible to configure an IDE to show warnings for exceptions which weren’t explicitly caught or declared. It could do so for all exceptions or only for those which would be checked in Java.)

This seems to give the best of both worlds: no boilerplate, and no pressure to catch exceptions unnecessarily; but still providing all the information about what exceptions are possible so you can handle them if you want to.

3 Likes

Unfortunately, your proposal does not solve CE issues.
Worse, I fear that it provides a false perception of safety.

I also have mixed feelings about checked exceptions and not including them in the Kotlin. I always liked strongly typed and very “strict” languages, so for many years of developing in Java, I kind of liked checked exceptions as they fit into my strict world nicely. However, they always caused a mess in the code and even after a decade of using Java, I still wasn’t entirely sure how to manage them, so they don’t cause problems.

Recently, I had to do a small task in Java, I wanted to use Stream.generate passing a function which throws and that immediately reminded me about all problems that checked exceptions cause. “Ohh, come on, I just want that exception to pass to the consumer of the stream, isn’t this a logical thing to do? Why the language fights me when I try to do a reasonable thing?”. Then, I created a sneakyStreamGenerate utility using @SneakyThrows from Lombok. And this proofs that checked exceptions sound nice in theory, but unfortunately they fail in practice.

I don’t know how to get pros of checked exceptions without introducing all these related problems. Above case could be maybe improved if we make the throws declaration generic, so Stream.generate receives a function returning T and throwing E and then consuming also generates T and throws E. But this is only this single case.

While using Kotlin I learnt to handle this by:

  • Do not use exceptions for known and expected erroneous states. Use sealed types, result objects, etc.
  • Assume every function call may throw. Catch wherever we need to not propagate further.
  • But do not think too much why it failed. It just failed.

As a result, I treat all exceptions as unchecked. Contrary to Java where catching Exception was an anti-pattern, in Kotlin I rarely catch specific types of errors. Mostly for interop with Java or other libraries that signal failure states with exceptions.

Still, I would be happy to go back and use checked exceptions if only we invent a better approach to them.

6 Likes

Thanks for the replies, good to hear that we are not the only ones facing the issue.

Yes, in our code sealed types is the way to go. I guess it can also be forced by the linter to disallow throws (have not done yet). However, this will leave “false safe” feeling, as other modules could still throw.

I also understand that simply requiring checks on exceptions is bit too late for the language, as it will break most of the existing code… but we have to start somewhere. This will become more important with Kotlin Multiplatform, as Swift has more throwing checks. Unfortunately I also do not have a solution for this.

What if (just an idea):

  1. If a function throws but is not marked with @Throws, on the line of throw compiler could have a warning/suggested action to add the annotation.
  2. If a non throwing function calls another throwing function, on the line of the call compiler could have a warning/suggested action to either wrap the call into try/catch or add some kind of annotation to this line itself like @UncheckedThrows.

These should be warnings and cause no compilation errors.

This way, slowly people will start marking their functions as @Throws and it will be possible to act on the exceptions, if someone wants to ignore it, they will still be able to do so.

Later, in 5 years or so, when most of the code will adopt annotations, we can think about whether these warnings should become errors (not sure if that would be needed though).

If you don’t want your app to crash because 3rd party library throws exception, just surround the call with try/catch and do whatever you need in case of exception. At least log it, so app can continue to run (if it can).

That means I need to put all calls to 3rd party libraries within try/catch block, which does not seem very productive. Remember null checks before optional? Question would be how to prevent it, just like optionals removed burden of null checks.

1 Like

To be fair, a third party library is also free to just throw an unchecked exception in Java, or even dereference a null pointer if it feels like it, so if you want to be absolutely sure library calls can’t produce error states you’d have to do that anyways. Though I agree that better documentation and linting tools would be helpful at the callsite.

2 Likes

I disagree, Optional did not fix any problem in a language with the million dollar mistake.
If that issue can be fix using a library, it should named “one penny mistake”.

However, you are assuming that checked exception is an improvement in Kotlin, however this isn’t proven.
In 25 years, Java highlighted how checked exceptions suffer from no fewer problems than they solve, to the point that no other language has adopted this design, including Kotlin.

Please consider to take a look to Kotlin official documentation: Exceptions | Kotlin Documentation

If the only problem is the hassle of surrounding 3rd-party calls with a try-catch block, you can always make a utility function that runs a lambda inside a try-catch block and returns a monad or something similar with the result and handles the logging. That should reduce the amount of boilerplate code to a bearable level, I think.

Thanks for the reply.

I don’t get this point. It absolutely fixed the situation for Kotlin, you no longer need to carefully check all possible variables for nullability, because type can tell you whether it is optional and can be a null or not. Then it is up to you how to handle those optionals. Without optionals, all variables could have been null, therefore you would not get help from compiler to prevent calling a function on a null object.

The same “safety” is missing from exceptions, you do not know which calls might throw or not, and therefore you can’t act on it. Therefore you have to defensively put calls into try/catch if you want to avoid potential crash from exception being uncaught. Developer has no possibility to prevent them and only put calls in try/catch that might actually throw something.

Again, comparing nullability, without optionals, all objects could be nulls, therefore to avoid nullpointerexception, you would defensively check for nulls for the objects you think might be null. With Optionals it is no longer the case, because you know which objects might be nulls and you check only their nullability and compiler helps on this.

We need similar for exceptions, we need to be able to tell which function call could throw and needs an extra attention, so that developer can handle error properly if they don’t want to get a surprise crash.

I am not sure about this, at least Swift does it quite well, and from our experience (also mentioned in the first comment ) we never had such crash there.

It’s not just about the logging though, sometimes we need to show a popup to users that some operation failed, sometimes we need to handle actual exceptions and maybe retry differently…

Goal is not to “swallow” the exceptions, but to really handle them and have visibility what could happen on the other side of the function call.

We know which calls may throw. Each and every of them. No matter if we have support for checked exceptions or not, there are still unchecked exceptions and we always have to protect against them (or let it crash).

Checked exceptions allow to provide an explicit “alternative result” from a function, but they can’t be used to determine which functions are safe or unsafe to be called.

1 Like

I suppose you are referring to nullable types, not optionals.

we need to be able to tell which function call could throw and needs an extra attention

I fully agree, in a perfect world every Kotlin developer would love to know all errors thrown, unfortunately there is no good way to do this, other already explained you multiple times.

It seems to me that you are trying to revive an old idea, systematically ignoring all the reasons why it has already been discarded.

Unchecked exception is not a bug, it’s a feature :wink:

I am not ignoring it, I also stated it in the initial comment, I fully understand the points, but I think it’s not black/white, I think we don’t have to fully ignore the situation and we can have a better situation then we have now.
There can be at least little bit of help from the compiler.

I mentioned above possible suggestions from the compiler that something potentially could throw. What do you think about it?

Yes, and this is the problem. In pretty much every language you can do something that could crash the app, we will not handle all of them.
However, crashes due to uncaught exceptions are "solved problem"s in programming languages, and Kotlin already has try/catch, now we just need bit more information and guidance from compiler to remind us that certain function call might require an extra care. I am not talking about hypothetical divide by zero.

2 Likes

now we just need bit more information and guidance from compiler to remind us that certain function call might require an extra care.

I think we all agree with that. But functional paradigm (among others) consider that exceptions are not the solution for that, because they represent an exceptional occurrence.

The solution offered in Kotlin and other functional oriented languages is to use the function return value as a container that represents the possible outcomes of the function (think Result, Either, custom Sealed class hierarchy, etc.).

A lot of people want checked exceptions, but a lot don’t (including me). At this point, I think that the sensible thing to do is to create a community annotation processor or compiler plugin that adds compiler warnings or errors based on some annotations or code analysis.

It has always be the purpose of Kotlin to be a lightweight language with high extensibility capabilities through libraries and compiler plugins, so I think it is what makes the most sense (and with K2 now delivered, it might become easier to do so).

For example, Arrow has created a library to add function features for people thinking the core language is not going far enough.
Compose has emerged as a new paradigm for composable UI editing using code.
Both of them have a huge success, and change quite radically language usage, but they do not force their opinionated choice in the langugage. For me, checked exceptions is the exact same thing.

1 Like

I think many people answered why we don’t want checked exception from multiple directions.
Let me add yet another perspective: the whole exception mechanism was designed to cut through the callstack in hope that somebody would be able to handle “exception”. It is not an alternative way to return the different result.
If you need to avoid crash by any means, do what Tomcat (for example) does: catch all Throable on the top level and handle it. Tomcat, for example, logs the exception and drops the request.
If you need to pop-up message for the user, catch exception at the place you need to pop it. In this case you won’t need to pass this exception value through multiple callstack frames. This is the only reason for the exception.
Your approach gives even less value if you consider that 3rd party library changed after you developed your code and now throwing yet another exception (even properly marked cked). You don’t know about it and your application still crashes (unless you take care of ALL exceptions on your site).

Note that with Kotlin 2.0 and the experiment around Union type for errors, we might have the best of both worlds (FP and java checked exceptions): we will be able to declare compile checked errors easily, in a standard way, with dedicated operators to handle them more easily, and that will be easier to to handle in lambda and generics, as the error case is part of the return type.

1 Like

Honestly, I don’t see too much in that proposal.
They define a entirely new kind of type, a new syntax for sealed classes and a bunch of new operators, fixing some trivial problem already well working with checked exception.

I really like to understand how these language’s changes perform with composition or inheritance.

1 Like