In future, could Kotlin have checked exceptions?

How’s that an insinuation? I only said that Eckel quoted some C# developer and that’s how this quote became popular. It doesn’t really matter who said it, what matters is that there is no proper scientific research behind the claim.

As to the rest of the post, I said right in my first comment that we don’t need to have checked exceptions in particular, so I’m not claiming in any way that checked exceptions are perfect. But right now Kotlin has nothing at all. I’ve already explained why sealed classes don’t help. Libraries like Arrow won’t help for the same reason.

Declaring an exception that is known to be recoverable is equivalent to declaring a more complex return type like a discriminated union of success and error values. This of course satisfies your requirement #1.

I think you stuck in requirement #2 to disallow to specifically disallow that, but it’s just syntactic sugar for returning the error out to the caller. I expect that if you were to argue that it’s more fundamental,
then you would be relying on a pattern of functions having secret knowledge about their callers, which is a horrible idea and doesn’t even support the premise.

So… Kotlin supports these kinds of return types, of course. Occasionally they are useful, and when they are you can just make a complex return type.

Granted, Java with its checked exceptions supports these kinds of discriminated return value pretty well, except that they can’t be folded into generic return types. That’s pretty annoying.

The big problem with checked exceptions in Java, though, is that they are used for other things, and it has caused a lot of problems. All the methods of OutputStream, for example, throw IOException. Is that recoverable? How could the author of that interface possibly know. It doesn’t even specify an error condition. It means only that it is an error that prevented the IO operation from succeeding. The OutputStream interface is independent of the target of the data, and doesn’t even know if there are any possible “recoverable” errors at all, yet it still declares the exception, and now you can’t connect a source to a ByteArrayOutputStream without somebody writing code that will never be called to handle these impossible “recoverable” errors.

It turns out that the vast majority of interface methods that declare a checked exception are written without any specific knowledge of the real errors that might occur, because they are in abstract interfaces like OutputStream. The abstract interface has no notion of which error conditions may actually occur, and which of them are recoverable. They might as well all just declare ‘Exception’, but it’s even easier if we just assume that everything does… like Kotlin.

The responsibility for handling recoverable errors should usually reside with the composition root, which decides which actual implementation will be used for the abstract interface. If I pass a FileOutputStream to a method that takes an OutputStream, then I have to know that it could run into space restrictions, and it’s my job to take care of that.

There’s no reason to mess up all the intermediary code, since checked exceptions don’t add any value at all in this situation.

2 Likes

I might add something more about how I use checked exceptions in Java. This might give you, the reader, an appreciation for why they have use, even if perhaps, this use may have a narrow scope.

My code creates instances of a thread sub-type I call a thinker. These thinkers have the following in their run method:

@Override
public final void run(){
    try{
        this.think();
    }catch(PonderableDilemma x){
        x.ponder();
    }
}

The abstract think method throws a ponderable dilemma, and the ponder method of this dilemma is also abstract; its implementation can take some corrective action that makes sense in terms of the ponderable dilemma that is thrown.

Another type of checked exception called an imponderable dilemma is thrown in almost all other methods of my code. Imponderable dilemmas are not ponderable ones; they cannot be caught in the run method of my thinkers and they do not declare a ponder method. This means that the developer must implement a ponderable dilemma that responds appropriately to any imponderable dilemma which makes sense to the context under which a given thinker is operating. At least as important is that the developer does not have to consider run-time context in methods which throw imponderable dilemmas.

I find this mechanism to be very useful; it forces the developer to determine whether there is a context under which a problem occurs, and gives the developer an opportunity to craft an algorithm that permits the system to take corrective action. For instance, the ponderable dilemma may spawn one or more other thinkers whose job is to take a particular corrective action.

Almost all features of Kotlin are syntactic sugar over Java, except for a few ones. If we are dismissing features on the basis that they are syntactic sugar, I might as well just write stuff in Java. Would you argue that data classes are not useful since they are just syntactic sugar for manually written methods? I don’t think so. Then why are you trying to argue that syntactic sugar is not important for stack unwinding? If there is no syntactic sugar for this, the code becomes really verbose. This is an important requirement.

Another problem with sealed classes I menioned, but you didn’t address, is that it doesn’t help when you work with Java code. Sealed classes don’t help with functions which don’t return a success value, but can potentialy fail, either.

Of course, he can. Do you really want to crash the program if the network failed or if the disk was busy? Effective Java has an item on this, by the way, which answers the question better.

And that’s why my first point reads “warn the developer” and not “force the developer”.

So you’ve fed an object capable of throwing I/O exceptions into a library. You have to handle it. How do you tell which methods of the library will now potentially throw these exceptions? And how do you make sure you didn’t forget to handle any?

1 Like

Maybe I’m not following some of the above, but I have a suggestion that seems to address some of these issues:

Kotlin should infer which exceptions a method or lambda could throw, without requiring an explicit @Throws declaration.

Java’s checked exception handling has two parts: if you’re doing something that could throw an exception, you either have to catch the exception, or declare it.

Of those two, catching an exception is a local matter; it’s all dealt with within the method, and no-one else needs to know. So removing that requirement doesn’t itself have wider effects. (It might help if an IDE warned about uncaught exceptions, for people who want to know about them.)

But declaring them is where you get the bigger interactions. And it’s a pain, because in almost all cases it’s boilerplate; the compiler already knows which exceptions you haven’t caught, and so could generate that declaration for you. (Kotlin added lots of type inference already; in some ways, it’s strange they didn’t add this too.)

This inference would improve interoperability with Java code: when calling Kotlin methods, it would always know which exceptions could arise, and so Java’s strong exception checking would still apply; and the declaration would still ‘bubble up’ to the point it’s caught, whichever combination of languages were used.

It wouldn’t make exceptions ‘checked’ in Kotlin; Kotlin code would still be free to ignore them. But the information about them would no longer be lost; at any point in the code you’d always know which exceptions could be thrown, so you could make more informed decisions about what to handle.

The problem case is around lambdas; that’s where things get painful in Java. But in Kotlin, most lambdas get inlined, so this shouldn’t be a problem! (I’m not sure what should happen where they’re not inlined; perhaps the compiler can do something akin to type parameterisation? Or maybe that would have to be a corner case it doesn’t handle.)

To me, this sounds the best of both worlds, giving developers all the information they’d get from checked exceptions, without requiring any extra code.

3 Likes

Of course not. I want to catch the exceptions that I know how to handle. It doesn’t matter whether or not they are subclasses of IOException. Checked exceptions don’t make the situation better.

Well, because “recovering” from an exception involves not just knowing which exception was thrown, but what operation was interrupted, and I know what the object I passed in was for, I know to expect the exception in the cases where I can recover from it. It’s not automatic, but… When you’ve fed an object capable of throwing IO exceptions to a library, how do you know which methods of the library will potentially throw these exceptions? It’s the same answer, really, but usually more complicated, because now you have to anticipate that they’ll be wrapped as the cause of other more general exception types. Checked exceptions don’t make the situation better.

1 Like

That would be a good step in the right direction! Both of the points I outlined would be covered by such a mechanism.

I think that most developers here have mostly dealt with Java before, so they only see the dichotomy of checked and unchecked exceptions and no other alternatives. There are definitely other ways of achieving the same thing. Rust, for instance, has solved this problem, why can’t we?

So what’s the problem here? If you want to handle more errors than the called function suggests, you can do that. If you want to opt out of handling some errors, you should be able to do that either. But in any case you need to know what errors can appear there first. Right now nothing helps you do that. No, I’m not advocating for checked exceptions. I feel like I have to repeat this in every reply.

You know what the object is for, but what you don’t know is how exactly the library is using it. Excluding, of course, the options of diving into the source or reading the documentation, both of which may be absent/poor. For example, if we take that same I/O operations, how do you know when the source you’ve put into the library is actually used, and when the library is using cached data and can’t possibly throw?

How do I know what methods of the library will throw? I don’t! And it’s not automatic. That’s precisely the problem and needs to be fixed.

By the way, you left my other point regarding the sealed classes unaddressed.

I think we all agree that we will not be able to introduce checked exceptions, and neither should we.

We also will not be able to just implement something like Rust has, because

  • that’s to big of a change to do at this point
  • It would not fit the JVM-style at all, and it would be hard to do Java Interop that uses that system
  • Only having Result like types without Exceptions only work’s if you have some kind of do-notation, Monadic chaining system or at least rusts ? system (add a ? after a Result returning function, and then - if it’s an error - the function will return that error, pretty much the equivalent of rethrowing in java)

What we also agree on is the fact that Kotlin should absolutely infer the @Throws annotation. Having no knowledge about what a function throws in the documentation of a Library is unacceptable, but there are many libraries that don’t provide that information in the Javadoc.
Also, currently, you’ll have to just know which of your functions might get called from java, and then know which checked exceptions could occurr, because you’ll need to add them to @Throws which is currently even more verbose than javas way of saying that a function can throw. If you don’t add that annotation, Java won’t remind you to catch it and IDE’s will say “hey, this exception can not be thrown, why are you trying to catch it?”.

That’s a big problem currently.

I think Kotlin inferring @Throws annotations is absolutely doable, at least when you don’t include the lambda stuff. Which you actually could: for inline functions or functions that directly call the lambda, you could generate the throws from the things that your lambda can throw without a problem. For more nested or inconcrete examples, you cannot really do anything, so we won’t find a solution there.

But that is not necessary, as this would not have any implications on actual compilability. Just IDE-support and java interop.

I’d love to hear someone from Jetbrains give some details into

  • why this doesn’t exist yet
  • how realistic it is to implement
  • what things we’re missing, especially in regards to inferred throws annotations
5 Likes

Why? Any function can throw any exception in general case (because of ability to replace class via ClassLoader). In real life, the most of functions doesn’t throw ArgumentException in production, so why should we catch it?

the annotation does not force you to catch it. Also, I’d say this should mainly be about checked exceptions.

The reason Kotlin should infer these is because that’s the only way you’d actually know what you have to catch. It’s hard to find out about the more rare exceptions in a dev-environment. I’ve had a couple of exceptions I needed to catch that I just didn’t know about before having the code deployed in a close-to-production environment. This is the result of IDEs not telling us what checked, recoverable exceptions a function can throw.

I really don’t see how anyone can oppose this statement. There is absolutely no damage done by having thrown exceptions show up somewhere, so I guess you just misunderstood me.

Also, yes any function can throw anything, but that’s at least unlikely. It’s not about having a perfect system or forcing you to handle each (maybe impossible) exception. It’s about seeing which exceptions could be thrown. because having this currently either requires a lot of ceremony in manually writing @Throws for each function that you write , and then for each function that calls that function, or you just don’t do it, which causes you to not see which exceptions are possible. also, catching Exceptions thrown by a not-annotated function from java is really strange, and most Linters / tools will tell you to remove the impossible catch. So thats Why.

3 Likes

That’s a very good summary. Just to be clear, I don’t propose that we do the exact same thing Rust does for the reasons you outlined, I’m only pointing out that other languages did think about solving the problem. Just like the semicolons: it was generally thought that dropping them would be error-prone just because JavaScript did that imperfectly for a long time, but eventually languages which solve this problem started to appear. The same thing is happening with the error handling, so why should Kotlin be left behind here?

Same here, right now what I usually see is either catching too many errors with catch (ex: Exception), or not catching something when it should be. The first case is bad since exceptions caused by programming errors end up in the logs at best and remain unnoticed instead of being fixed. The second case is obviously also bad. I had a former colleague who had to rely on QA to uncover possible thrown exceptions and only then put in appropriate error handling blocks.

In my opinion, that’s the only really weak spot in Kotlin, and it would be very nice if the language developers addressed it.

1 Like

Kotlin needs checked exceptions… for sure.

It is funny to see the “official” video by Nat Pryce and Duncan McGregor.

They complain about catching every Exception in a long cascade (why not join them or rethrow specific ones). They hate merging exceptions into a more fitting one (here: BadRequest exception).

Followed by Nat asking Duncan a very difficult question about “where is the error in the code”:

This demonstrates why checked exceptions are so important. Duncan did not have to search for the error, the compiler would see the checked JsonParseException in Java.

The paradigm to develop an elegant language is nice, but the primary goal of a computer language is to write robust applications. The end user never see the code, but he see the application crashing if there was any “unchecked” (not found) exception.

So forcing the programmer to handle checked exceptions will ensure better code… not prettier code.

Some often referred articles to “why not using checked exceptions” are written by Microsoft programmers. Be all remember all the buggy, crashy and unreliable Windows versions. Should you use this articles as positive reference.

The forced exception handling of java resulted in tons of reliable software (mostly server architectures), so that will be a good reference.

2 Likes

The forced exception handling of Java also results in shedloads of exceptions being caught where they shouldn’t and badly handled — sometimes with nothing but a log entry, sometimes not even that — just to shut the compiler up.

Java’s approach is far from perfect; and several posts earlier in this thread express the problems better than I can.

On the other hand, Kotlin’s isn’t perfect either. (Only last week I hit a bug that turned out to be because a library changed one of the exceptions it throws, and the Kotlin compiler didn’t warn us to handle it.)

So we’re looking for the best compromise. (As mentioned above, my suggestion is to make uncaught exceptions a warning, and for the compiler to infer @Throws declarations without needing them explicitly.)

2 Likes

That is exactly the point: you should not catch each exception, just catch the exception where you can handle them with a correct action. Just throw the exception one level up with a simple “throws SomeSpecificException”.

So specific exceptions are never forgotten. They should be handled in the best fitting level of the calling hierarchy.

Not in a global “catch(all the stuff I forgot)”

Referring to the Topic “Exceptions are part of the API”

Yes they are! And they should not be hidden by design.

Please have a look at android:

While converting more and more to a “jail-OS” more and more SecurityExceptions are thown (hidden runtime exceptions).

So if the API-Level changes, the code just crashes. Android will respect the API-Level noted in the Manifest file until “game over”. So the API changes under the hood.

What if all exceptions would be checked-Exceptions?

If I compile the code against the new API, several compilation errors will popup: “unhandled SecurityException”. Whats bad about it?

Sure the programmer has to fix the exceptions with some “ask for permission” code. But this is exactly our job. And the checked-Exceptions show us what and where we have to look at. It is a sort of convenience.

One of the major issues with checked exceptions is that almost nobody converts contingencies (= error related to what you are doing, and you are likely interested in so you can take alternative actions) to faults (= implementation detail that you are very unlikely to be able to cope with).

Contingencies should be checked so you have robust error handling. Faults should be unchecked, so you do not bother clients with errors they are most likely not able to handle.

Faults have to be reported somewhere of course, so you can resolve the problem. But it is unlikely that client code is able to resolve the situation.

An example: RemoteException is a checked exception if you are directly dealing with remoting, because you might want to retry the call, fail over to another instance, etc. But when calling EJBs it is unchecked because the clients of your EJB interface probably do not know what to do with it, besides re-throwing it. JEE should have never required the exception to be declared on each method, and should have automatically converted it to an unchecked exception.

See Effective Java Exceptions.

I would not mind good checked exceptions in Kotlin, if the Java mess (RuntimeException a sub type of Exception? Signals with special semantics (i.e. InterruptedException) mixed with regular exceptions?) would be cleaned up and the Java community can agree on a sane exception design. But that seems unlikely to happen giving the almost 25-year history.

1 Like

“The clients” are build by us (the programmers). So the API designer (another programmer) should not think about “what his colleague can handle”.

You are right, the mixing of (un-)checked exceptions is a mess but not an argument from throwing away the possibility of checked exceptions. Kotlin should be compatible to Java.

I think the difference is quite simple. Exceptions where is NO chance to handle, should be unchecked, all others should the checked.

checked: SqlExceptions, IOExceptions, SecurityExceptions

unchecked: OutOfMemoryException, IllegalJavaOpcodeException

also unchecked: IllegalArgumentException, that should be handled while coding. If a method got bad values like an out-of range index, there is no possibility recovering it in runtime environment

Kolin makes a large incompatible step forward with the great “non null” handling to protect the programmer from possible mistakes. I think the removal of checked exceptions do exactly the opposite.

The major issue with checked exceptions in java is the throw/try/catch syntax.

I fully agree with you.

Wherefore Kotlin adopted this construct to be compatible to Java,
but hides all exceptions, so you did not have to write so many try … catches :slight_smile:

I saw are some design patterns in Kotlin where you hide the try … catch surrounding

doSomething().onError( doRepairTheError() )

but this extensions do not help to remind me about possibly thrown exceptions.

I think this would become unreadable if the doRepairTheError() would be a multi-line code fragment. So are there any other alternatives to the try … catch construct?

Uh, that is your job as a software developer: design good APIs.

Suppose you make a data access abstraction API. Should it throw file, SQL, MongoDB, etc. exceptions, which tell the client absolutely nothing besides something like: your configuration or infrastructure is broken? Wrapping those in API-specific exceptions adds no value. I prefer an API that does not bother me with implementation details like that. If something breaks without there being much I can do besides informing the user, make it unchecked so my fault barrier can pick it up.

What I would like to know is that if I, for example, try to retrieve some data using a key, whether or not that data exists. A checked exception may be helpful here. The same is true for violating integrity rules when modifying data. Those are things for which I can offer the user a meaningful response.

I was not using it as an argument. I was stating how exceptions are generally used. I understand that this can make people dislike checked exceptions.

There is way more to a good exception strategy than a simple division between checked and unchecked. However most people are not interested in these sort of pesky details, and want to keep things “simple”,

I doubt we will ever get:

  1. A very easy to grasp and use, flexible exception handling construct in a language. (Although I personally think Java’s implementation is pretty good.)
  2. A common agreement and understanding of the not so simple proper classification of exceptions.
  3. The correct implementation of such a classification throughout standard and third-party libraries of a platform.