Types help the programmer. When the compiler gives me a type error, it is telling me about something I messed up and that would otherwise be an error at runtime. Sometimes the type system is wrong and I need an escape hatch. A type system that is wrong too often isn't very useful. For the majority of exceptions, there no useful thing that can be done at the call site to deal with them beyond bubbling them up the stack. A type system that constantly makes me explicitly deal with such errors is making my job harder, not easier.
There are plenty of errors/exceptions that don't need to be bubbled up the call stack. I don't think that's the main issue with them. Like you say the issue with checked exceptions is that there is no escape hatch to shut the compiler up and panic if you can't handle an error or its not possible. They desperately need a short hand like Rust's ?, Swift's try?, or Kotlin's !!.
A a;
try {
a = someFnToGetA();
} catch (AException aex) {
// not possible in this situation
throw new RuntimeException(aex);
}
In a modern language that has checked errors that just becomes something like:
val a = try! someFnToGetA();
val a = someFnToGetA()!!
As another example, the exception type hierarchy doesn't pull enough weight. Exception is the base class of all checked exceptions and RuntimeException is the base class of all "ordinary" unchecked exceptions, but it confusingly subclasses Exception. So there's no way to catch only "all checked exceptions". Then, Error is distinct from that hierarchy, but some things that smell like errors were made into exceptions instead (e.g. NullPointerException).
This was compounded by the fact that, in the original design, you could only call out one exception type in a catch statement. So if you had 3 different disjoint exception types that you simply wanted to wrap and rethrow, you had to write 3 different catch blocks for them. Java 7 added the ability to catch multiple exceptions in the same block, but it was too little, too late (as far as redeeming checked exceptions goes).
> So if you had 3 different disjoint exception types that you simply wanted to wrap and rethrow, you had to write 3 different catch blocks for them.
Agreed. There's a proposal for exception catching in switch [0] which I'm hopeful will alleviate a lot of this. I think that jep plus combining exceptions with sealed types the error handling will be convenient and easy.
sealed abstract class SomeException extends Exception permits AException, BException {};
void someFn() throws SomeException;
// hypothetically handling in switch would let you enumerate the subtypes of the exception
var a = switch (someFn()) {
case A a -> a;
case throws AException aex -> new A();
case throws BException bex -> throw new RuntimeException(bex);
};
> As another example, the exception type hierarchy doesn't pull enough weight.
Kotlin has an interesting proposal for their language that creates their own "error" type that will allow type unions [1]. The only thing I worry about is that it further puts Kotlin away from Java making interop a lot harder.