In future, could Kotlin have checked exceptions?

I agree but even than I cant decide in LevelManager which Errors will be handled in LevelManager itself and which one will be passed to the UIScreen and will be handled there

a simple forcecatch annotation or something like that would be so nice just to tell the compiler here is an error this breaks my complete Architecture of Error Handling, in Java it was so nice you could always decide on which hierarchy you want to fix your Errors and push some of them into the higher hierarchy when needed

You can, but it requires yet another sealed class. I agree it is a bit more tiresome.

But even then I have to use When with expression all the time to cover up all possibilities I think Kotlin need a nicer way for Error Handling or at least some way to force the Method Caller to handle with possible Errors because dealing with Errors is really necessary in every Language

The union return type (with the sealed classes) approach to error handling is a functional approach. You don’t have when statements all over the place, instead you use a map/apply function that will only be invoked by the success path. Only when you want the end result do you need to handle the option of an error. But in this case your errors are predictable and you don’t have to handle partially changed state etc. that you need for exception safety. You also don’t have to handle “foreign” exception types (or exception wrapping) which is one of the major issues with checked exceptions.

1 Like

That is not the general finding of those that have studied the matter. The oft quoted counter to that is:

Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result – decreased productivity and little or no increase in code quality.

Ask yourself, why other languages have not adopted checked exceptions.

4 Likes

To have the Option is always a good thing. I still believe with many other People that the ability to force checked Exceptions would gain huge benefits.

I saw this multiple time in my career and many bugs and problems could be saved on that way.

A optional force operator would be amazing.

1 Like

Ask yourself, why other languages have not adopted checked exceptions.

Kotlin is not other languages. Kotlin claims to have seamless interop with Java APIs. Many Java APIs are written with the expectation that the consumer of those libraries will get a compiler error if they do not catch the checked exceptions. This makes those libraries highly error prone when calling from Kotlin.

At the very least, it would be great to support checked exceptions at the Java-interop level as a compiler error, with an associated @Suppress annotation for disabling the compile error per line/class/etc.

3 Likes

If unchecked exceptions are the only variety of exceptions in Kotlin then they should never be used to handle non-fatal sitations. Now proof by counter example. Imagine I have written an HTTP client in kotlin that offers a synchronous “get” method that throws an exception upon network error. Correctly written code in kotlin should look like so:

try {
   client.httpGet(..)
} catch (e: NetworkException) {
   // show error to user
}

The Kotlin code must be written exactly like Java to be correct but the Kotlin compiler is not helpful. If this were Java and the user forgot to put a try/catch the compiler reminds them. In Kotlin the compiler fails to remind the programmer if they forget the try/catch then their program crashes on network failures.

If one of Kotlin’s goals is to get programmers to write safer code that is less likely to crash then the authors of Kotlin took one step forward by forcing null to be handled safely everywhere so programs almost never crash with NullPointerException and then one step backwards by allowing any other kind of exception to crash your program without help from the compiler.

Observe that Ktor does use exceptions when invoking get: https://api.ktor.io/1.2.6/io.ktor.client.request/get.html

The only way to know it uses exceptions is to read the documentation and we still don’t know which exception types will it throw? If I catch plain Exception then I catch fatal usage errors such as IllegalArgumentException, ArrayIndexOutOfBoundsException, etc which have nothing to do with network.

Ideally Kotlin would disallow catching exceptions completely (or possibly treat them like Unix signals and just jump to a single global exception catcher), that would force exceptions to only be used in fatal situations. Then for Java interop Kotlin would require any java library that uses exceptions for normal operation to be wrapped in a java wrapper that converts non-fatal exceptions into some other kind of error handling model.

3 Likes

I should be able to write, for example, a fun wordCount(src: CharSequence): Int

I should be able to write a file-backed implementation of CharSequence, say FileCharSequence

I should be able to pass a FileCharSequence to wordCount to count words.

Using a file, though, implies the possibility of IO errors. That should certainly not be handled with a checked exception, because the CharSequence interface should not have special handling for anticipated IO-backed implementations. There are too many kinds of implementations for it to anticipate, anyway. (Look how many IOException subclasses have nothing at all to do with IO!)

But, an exception is absolutely required, because if FileCharSequence encounters an error, then wordCount cannot proceed.

However, this exception certainly isn’t fatal, because the code that up-casts FileCharSequence to CharSequence knows about the potential IO errors, even though wordCount does not. That code can catch the exception and handle it appropriately.

It would be nice if some language included the ability to declare these exceptions and ensure that they are caught (e.g., class FileCharSequence : CharSequence except IOException {...}), but Java’s way is a broken anti-pattern that is best ignored.

3 Likes

In my opinion, the best way would be some way to tunnel checked exceptions through code that doesn’t know about these exceptions, especially through lambda expressions. For example a function that takes a lambda expression should just be able to say, “when you execute me, I’ll possibly throw the same exceptions the lambda expression throws”, and then the lambda expression throws a checked exception that is automatically detected at the call site of the method that takes the lambda. Example:

fun readStringFromFile() : String throws IOException = TODO()

fun doSomething() throws IOException {
    with(something) {
        readStringFromFile();
    }
}

In this case Kotlin would allow you to catch the exception either as usual where you call the method, or outside of the with call, because with would silently pass through the exception or you could just pass the exception to the caller of the doSomething method.

1 Like

Technically you can achieve this using generics. Here’s Java example:


interface CharSequenceX<E extends Exception> {
    int next() throws E;
}

class StringCharSequence implements CharSequenceX<RuntimeException> {
    @Override
    public int next() {
        return 1; // just an example
    }
}

class ReaderCharSequence implements CharSequenceX<IOException> {
    @Override
    public int next() throws IOException {
        throw new IOException("Disk error");
    }
}

    static int stringWordCount() {
        return wordCount(new StringCharSequence()); // does not require throws
    }

    static int readerWordCount() throws IOException {
        return wordCount(new ReaderCharSequence()); // requires exception handling or throwing
    }

    static <E extends Exception> int wordCount(CharSequenceX<E> seq) throws E {
        return seq.next(); // just an example
    }

but it’s more of a gimmick rather than proper solution:

  1. It’s not possible to declare multiple exception types, e.g. IOException, SQLException without declaring multiple generic type parameters.
  2. It’s necessary to write this boilerplate for every higher order function.

But it’s just an idea which could be extended and provide proper solution for this particular problem.

PS I’m not arguing that checked exceptions are a good idea. I don’t think they are. Every language ditched them, including even Java in practice. But this particular problem could be solved.

1 Like

I would argue that the “proper” way to implement an HTTP get method in Kotlin is for it to return a sealed class, with one subclass for a successful result and one for errors (I might even split the error class into one for connection errors and one for HTTP errors). The we get something like:

val result = client.httpGet(...)
when (result) {
    is Success -> // handle success
    is ConnectionError -> // handle connection error
    is HttpError -> // handle HTTP error
}
1 Like

What you are asking for is to use a monad. That is probably the cleanest way to handle errors, and even C++ seems to be moving that way. The Arrow library for example has the Try type to do this.

The standard library actually has the Result type instead (although I’m not sure if it is stable - it was introduced for coroutines, and it is an inline class)

If you look at the Error-handling style and exceptions section of the Result KEEP, they suggest using a sealed class instead of Result for something like this.

1 Like

Thanks, your argument that returning a sealed class is the proper way appears to be backed-up by the example of FindUserResult at KEEP/result.md at master · Kotlin/KEEP · GitHub

That being said there’s nothing enforcing that pattern and the authors of the Kotlin language left checked exceptions in Kotlin and freely let them crash applications. If exceptions aren’t supposed to be used for basic error handling in Kotlin then there should be something to prevent their use in that way or at very least strong warnings to developers at compile time. Imagine if IKEA shipped their furniture with an assembly tool that simply damages your furniture and then in some tiny print somewhere says you shouldn’t use that tool. Expect to see a lot of damaged furniture.

I can freely write the following code and neither the compiler nor the IDE warns me that this will likely crash my app by an exception if the file doesn’t exist:

fun readByteFromFile(s : String): Int {
    val x = FileInputStream(s)
    return x.read()
}

The question isn’t what is the right way to write this code, the question is how can the authors of Kotlin prevent developers from writing the wrong code. A lot of time and effort was put in to preventing NPEs in Kotlin so clearly safeguarding developers is a goal of the language.

I can freely write the following code and neither the compiler nor the IDE warns me that this will likely crash my app by an exception if the file doesn’t exist:

This function can raise error because of a at least the following causes:

  • No enough operating memory
  • Thread/Coroutine are terminated
  • Input string is not integer
  • IO: file is missing, device was ejected, handle was closed externally, network timeout (for network FS), access denied, etc.
  • Method is not implemented/supported
  • Stack Overflow

Question: what details (and how) do you want to receive at compile time? And why exact these? And how can we support new errors in future?

1 Like

You should really expect that anything can throw an exception, unless you have a very good reason to think otherwise, so consider yourself pre-warned, and be thankful that the language has default error handling that returns the errors up to the caller.

I guess my argument is still not really clear. If Kotlin didn’t interop with Java in the way it does today I wouldn’t be making any claims. My argument is that many Java APIs use checked exceptions to handle situations that are expected and should not be considered fatal during the course of normal program operation in situations API designer deems recoverable. Programmers working with these particular APIs in Java absolutely must try-catch these particular exceptions because that is the only valid way to interact with these particular APIs. Because Kotlin allows for and is designed for Java interop these same rules apply to Kotlin.

We are accustomed to file based IO always succeeding but there are instances where it might fail and you probably don’t want your program to simply crash. Lets say I inserted a memory stick into my computer with an essay on it and I was editing that essay in my text editor, I finished my edits after an hour and hit save but realized I had accidentally remove the memory stick earlier and now my program crashed and my work was lost. This was a somewhat rare but recoverable situation that the Java API designers foresaw and intended the developer to catch and handle which is why they throw the checked IOException when reading and writing IO streams. To use the Java stream API correctly the developer should be forced to consider the checked exception.

I will give another example, take the following code:

  val signature = Signature.getInstance("SHA256withRSA")
  signature.initVerify(publicKey)
  signature.update(message)
  return signature.verify(signature)

It looks completely reasonable, the verify method returns a boolean telling the caller if the signature is valid or not. But what the developer forgets when writing this code in Kotlin is the API designer added a third possibility, the verify method will throw SignatureException (a checked exception) if there is anything malformed about the signature. For example if the signature is supposed to be 256 bytes in length but you pass in 255 bytes then it throws SignatureException instead of false. This is highly possible if infact there is any kind of data corruption in message transmission (very normal in the course of operation) or an attacker is trying break the system. The point here is that the API designer intends the developer to use checked exceptions during the normal course of program operation. The API developer does not intend for your program to crash if the signature format is invalid, and the developer almost certainly doesn’t want their program to crash either. Kotlin unfortunately makes that possibility quite likely.

One could make the argument that the use of SignatureException makes for a poor API and messy code and I wouldn’t disagree but this is simply what the API designers chose and using this API correctly requires the developer to go along with it despite stylistic opposition. Again I’m not saying checked exceptions are good or bad. My point is merely that they are part of the existing API design and that Kotlin is supposed to allow developers to work with these API. The philosophy of Kotlin is also distinct from other languages such as C and C++ in that Kotlin aims to be a safer language and prevent knowable coding mistakes. This is why the designers of the Kotlin language went through such pains to prevent NPE. Thus we arrive at the mismatch between the stated goals of Kotlin and the actual implementation of the language with regards to the handling of checked exceptions.

OutOfMemoryError and StackOverflow are not really easily recoverable and are not part of any typical API interface, also they aren’t checked. I am taking about only checked exceptions that are declared by APIs and are clearly recoverable by the user of the API.

Again none of this would be an issue if Kotlin didn’t allow interop with Java APIs in the way that it does today.

2 Likes

In my experience, though, a lot of exceptions end up being propagated to some top-level handler (web app controller, GUI event loop, etc.) which logs and/or displays any error, then carries on - so work is not lost. If lower levels make sure app state remains consistent when any exceptions happen, all is well. They probably need to do that anyway, because there are plenty of runtime exceptions which can happen in reasonable situations - or are thrown by unreasonable but useful/mandated libraries in use. So, I don’t think checked exceptions really buy you data safety, and may give a false sense of security. Also, in the world of interacting processes, just handling your own internal exceptions may not be enough to keep data safe anyway!

2 Likes