Disclaimer: This will, to a degree, be my own personal take on the problem. I love programming language design, I've thought, read, and discussed a lot of about PL design in general, and the hard topic of errors in particular. This gives me some experience, and also an appreciation that beyond objective factors, there is a lot of subjectivity at play. I will try to separate objective from subjective, but I am biased and therefore may err.
TL;DR: Checked Exceptions are poorly integrated in Java.
However, checked exceptions are widely disliked. The C# designers made the deliberate decision to not have them.
Be careful about generalizations.
The current incarnation of Checked Exceptions in Java is widely disliked. The reason for the dislike, however, may be either Checked Exceptions themselves, OR the current incarnation in Java. And possibly a mix of both.
As a simple example, look at the interface of
Stream: if you want to use
map, your predicates cannot throw a Checked Exception.
The underlying issue here is that the Java language offers no way to easily handle, and manipulate, lists of Checked Exceptions in a generic context. At the end of the day, I'd like to be able to write:
<R, ME..., FE...> R transform(
Function<? extends T, ? extends R, throws ME...> mapper,
Predicate<? extends R, throws FE...> filter
throws (ME..., FE..., -BarException)
catch (BarException e)
And I can't.
Due to the rise of Generics, and of Functional Programming idioms such as
Stream, Checked Exceptions have become increasingly more inconvenient as time passes.
We could stop at poor integration and call it a day, concluding that Checked Exceptions in and out of themselves are perfectly fine, and Java just botched it.
A closer look, however, reveals that Exceptions themselves are part of the issue in the first place. Specifically, the main problem of exceptions is that they are not return types.
One of the advantage that Rust or Haskell have in using Return Types to signal Errors is that any advance that allows better compile-time manipulation of the Return Types (meta-programming) simultaneously grants better compile-time manipulation of Error Specifications in function signatures.
On the other hand, using Exceptions means that efforts must be, to a degree, duplicated between meta-programming on Return Types and meta-programming on Exception Specifications.
This comes to a head in Java because Java doesn't support Variadic Generics, but supports Variadic Exception Specifications. It makes it even more complicated to provide proper meta-programming facilities for the Exception Specifications... and is likely the root cause of Java designers having seemingly thrown their hands up.
A language could choose to provide proper meta-programming facilities to manipulate Exception Specifications in signature, and in that case Checked Exceptions would feel first class. Yet, even then, I expect that it would still not be as convenient as manipulating Return Types, because you would then need to manipulate both Return Types and Exception Specifications, leading to extra work -- as demonstrated in computation of
noexcept clauses in C++.
The rise of Generics -- notably for Functional Programming -- has led to a rise of Meta Programming which requires manipulating function signatures at compile-time.
Exception Specifications are not as convenient to manipulate in function signatures -- at best they double the work, as Return Types must always be manipulated regardless -- and may not support manipulation at all as it requires extra-work on the part of language designers and language implementers.
Checked Exceptions in Java require Exception Specifications in a language which does not allow manipulating them via meta-programming, making their use awkward to impossible in Generic code, which is increasingly prevalent.
By comparison, Rust's
Result type benefits from all the meta-programming machinery available in Rust in general -- without extra expense from designers, implementers, or users -- and therefore offers a much smoother experience.