Checked exceptions failed because 99 times out of 100 the exception is not recoverable, so the try/catch block is just wasting everyone's time. (In 7 years as a Java dev I can think of one time I wrote code that tried to recover from IOException instead of just making the caller retry.)
Even when the exceptions is theoretically recoverable, it has to get propagated up properly to the caller who should be handling recovery from it. But checked exceptions don't propagate sanely through executors and across RPC calls, so good luck with that.
The Task type in C# with the await unwrap sugar is similar to this. You can even check for and grab the exception without throwing if you want to.
Still, it's too bad the error type must be a throwable. I kind of wish it could just be a plain type so you can error or cancel without generating stack traces. Awaiting a failed task could still throw.
Would be a nice perf boost. As it is now, you don't want to actually cancel or fail a C# task in performance critical code. You need to successfully complete the Task and return an error, which is pretty confusing.
"throws IOException" is too much code? Or is the issue more that you can't really do autocoercion to a declared thrown type in Java the same way that you can do in Rust?
Proliferation of types is an issue in Java, but the whole language has that problem. It's not just exceptions.
Though I mostly agree it's slightly more subtle IMO. They failed because the writer of the method cannot know what the caller can recover from but must decide before compile time. The caller gets little say.
I don't think they are meant to be recoverable from, but instead they are a way to provide a controlled shut down of an irrecoverable failure, or to limit the blast radius of a localized failure.
In that sense, they are quite useful. Saving the file generated an exception - instead of suddenly closing the application, display an error. Or maybe a database is not accessible anymore. Instead of suddenly ending the service, trigger a controlled shutdown (log, send alerts, etc).
I don't think anyone would expect exceptions to propagate through RPC calls. A call fails, probably containing a description of the fail. Why should it propagate?
There are ways to handle errors and return errored RPCs without exceptions, such as internal error codes. But error codes are just as irrecoverable as exceptions.
In a desktop application that is not allowed to totally crash, or at least has to crash kind of gracefully, checked exceptions are useful.
But in the world that most Java devs live in, which is various flavors of RPC server, failing requests is fine. If lots of requests fail, your monitoring infra should page someone, and that someone will go log spelunking and figure out what's broken.
Very occasionally it turns out that the thing causing the RPC failures is a recoverable exception, and then you should wrap the problematic stuff in a try/catch block. (Often you'll wind up having to detect the recoverable error case by conditioning on substrings in the exception message, which the library owners will arbitrarily change in future releases. So make sure to write regression tests so you'll catch this when you upgrade the library. Java is fun!) But 99% of the time the failure is "network is busted" or "config was invalid" or "hard disk failed" and you should not be defensively programming against all those possibilities.
> you should not be defensively programming against all those possibilities
This is where libraries and frameworks come into play - they defensively program against that for you. And wrap it all up in a simple interface with, well, checked exceptions.
If I need to write some bytes to an S3 bucket but the network is hosed, there's literally nothing useful I or any library can do until the network is back up.
RPC calls will fail, error logs should get written, and a monitor should get triggered, and someone should get paged so they can wake up and figure out why the network is hosed.
Nowhere in there is it useful for me to wrap all my S3 writes in try/catch blocks.
Error logs and monitors find out because the RPC fails, which causes an error to be logged and a failed request metric to be incremented. (The UncaughtExceptionHandler will do the same for stuff failing in background threads.)
If eventual consistency is important, enqueuing for retry has to be done BEFORE you try to write, not on write failure. If/when the write succeeds, you remove the item from the retry queue.
I always feel like the error domain changes as you go across program domains.
I keep returning to something about error handling that bothers me. People argue what the proper way to handle errors is without really considering that it's highly context sensitive. Which makes me think you should be able to pass an error handler down to lower level functions that tells them what to do when something bad happens. Sort of like recent ideas where you pass functions a allocator instead of them calling malloc() directly or whatever.
Is this a problem with checked exceptions or a problem with Java? Couldn't exceptions be treated as robustly as param and return types in the generic system so they are more composable? Why not a Future<V, E1> interface?
The argument that some exceptions will always be runtime (like oom) so checked exceptions are flawed is harder to argue against but I would also say it's a matter of opinion whether you feel like it's worth dropping entirely.
You can return union types in Java if you want that, although it'd be nice to have better first class support for them.
The issue though is that forcing people to handle irrecoverable exceptions is just kind of dumb. If I do file IO, I know it'll barf sometimes. Sometimes I care, but the vast majority of the time I don't.
A lot of Java servers have RPC endpoints that basically say "write X to file Y" or "read X from file Y" and if the S3 connection is busted, there's nothing the server can do about that. Someone has to go log spelunking, figure out what's wrong (network is misconfigured, AWS is having an outage, whatever) and fix it. So it is bad API design on Java's part to make every single one of those RPC endpoints wrap every single thing that does file IO in a try/catch.
Even when the exceptions is theoretically recoverable, it has to get propagated up properly to the caller who should be handling recovery from it. But checked exceptions don't propagate sanely through executors and across RPC calls, so good luck with that.