- Java's exception creates stack trace by default
- Type erasure which prevents you from doing anything "fancy" with try-catch + generic's type parameter
- You can only throw something that extends `Throwable` rather than any type `E`
- etc
But yeah sure, if Java somehow provided a standard interface & an associated operator like Rust's Try trait to handle checked exception, it would probably have a much better reputation than it has now.
switch (result) {
case Ok(var value) -> println(value);
case Err(var ex) -> switch (ex) {
case HttpException httpEx -> {
// do something like a retry
}
default -> {
// if not, do something else
}
}
}At $DAYJOB, I'm currently migrating a non-performance sensitive system to a functional style, which requires defining basic functional types like Option<> and Result<>. So far I think it works pretty well and certainly improves edge case handling through explicit type declaration.
My current stable incarnation is something like this:
sealed interface Result<T,E> {
record Ok<T,E>(T value) implements Result<T,E> {}
record Error<T,E>(E error) implements Result<T,E> {}
}
Some additional thoughts:- Using only 1 type parameter in `Result<T>` & `Ok<T>` hides the possible failure state. `Result<Integer>` contain less information in type signature than `Integer throwing() throws CustomException`, leaving you fall back on catching `Exception` and handling them manually. Which kind of defeats the typechecking value of `Result<>`
- A Java developer might find it unusual to see `Ok<T,E>` and realize that the type `E` is never used in `Ok`. It's called "phantom types" in other language. While Java's generic prevents something like C++'s template specialization or due to type erasure, in this case, it helps the type system track which type is which.
- I would suggest removing the type constraint `E extends Exception`, mirroring Rust's Result<> & Haskell's Either. This restriction also prohibits using a sum type `E` for an error.
- In case you want to rethrow type `Result<?,E>` where `E extends CustomException`, maybe use a static function with an appropriate type constraint on `E`
sealed class Result<T,E> {
public static <E extends Exception> void throwIfAnyError(Result<?,E>... );
}
- I like the fact that overriding a method with reference type args + functional interface args triggers an "ambiguous type method call" error if you try to use bare null. This behavior is pretty handy to ensure anti-null behavior on `Option.orElse(T)` & `Option.orElse(Supplier<T>)`. Leaving `Option.get()` as a way to "get the nullable value" and a "code smell indicator"Improved checked exception handling in Streams would be nice, but that's my only gripe with them. They force you to stop and think, which is a good thing.
There are some checked exceptions that should probably be unchecked, like UnsupportedEncodingException thrown by ByteArrayOutputStream::toString which takes a charset name. I'm sure there are other subclasses of IOException that could be an IllegalArgumentException instead. But those few inconveniences don't invalidate the concept of checked exceptions.
What is the problem with Checked Exceptions? Don't they just provide type checking? I would think that developers would want to know which function might or might not execute a non-local goto in middle of execution and handle that properly, since debugging that will be a mess.
(a) pollute many methods on many layers of your class hierarchy with `throws X`, potentially also polluting your external API
(b) catch, wrap and re-throw the checked exception at every call site that could throw it
(c) Use Lombok's `@SneakyThrows` to convert a checked exception into an unchecked one. Which they advise against using on production code, and which I have definitely never used on production code.
There are specific situations where checked exceptions work well - where you want to say to specific code "I can fail in this specific way, and there is something sensible you can do about it". But those are fairly rare in my experience.
Personally, I think the problem isn't the checked exception itself, but rather the exception handling mechanism & popularity of unchecked exceptions. This led to some people misusing empty catch blocks or rethrowing with unchecked exception. Java 8+ functional interfaces / lambdas also don't play nicely with checked exceptions. There's no new keyword/feature/interaction between lambdas & checked exception, only old try-catch.
To this day (Java 24 and soon LTS 25), AFAIK there's 0 plan to improve Java checked exception. And there are a lot of quirks if you try to use checked exception with modern Java features. It's just all around painful to work with, and I prefer to use Result<T,E>-like with `sealed` sum-type nowadays.
It's quite weird the direction Java, C#, Javascript communities take on this. Lots of people just go with unchecked or using nullable reference. Which is fine for a throwaway program, but quite painful in a larger project. What do you mean a subtype / child class doesn't provide a supposedly "must be implemented" method `abstractMethod()` and instead throws an unchecked exception when you call it?
You can even see this in JDK standard library, take java.util.Collection.removeAll() for example (https://docs.oracle.com/javase/8/docs/api/java/util/Collecti...). A class implementing `Collection` interface might or might not throw unchecked exception, yet you can still do this and get runtime error:
Collection<T> c = new ImmutableList<T>();
c.removeAll();
Meanwhile, modern PLs with a better type system (e.g. Haskell, Rust, Zig) don't shy away from using Result<>-like and provides special syntax like Rust's Try trait operator (?) or Haskell's bind (>>=). This is pretty much similar to Java's checked exception in a lot of ways (explicit function signature declaration, type system enforcement, short-circuiting property, non-local goto). It's kind of sad that Java completely neglected checked exceptions, leaving them like an abandoned child rather than improving them like other modern PLs.wut?
If all that code throwing IOException could express better the type of fault, and if the error handling could them in a more nuanced, less awkward manner, it would be much better than it is.
But other, even more common source of hate is that many people simply do not want to handle them. They prefer if they can kind of pretend that stuff does not exists and look like "10x fast developers". You are technically faster if you ignore all that stuff while someone else fixes your code later on in different ticket.
Sometimes I want to write a small program for a one-off task and handling Exceptions is absolutely unnecessary.
All Java needs is better syntax to handle exceptions. Nobody wants to open a whole block to handle an expected exception.
``` switch (f.get()) {
case Box(String s) when isGoodString(s) -> score(100);
case Box(String s) -> score(50);
case null -> score(0);
case throws CancellationException ce -> ...ce...
case throws ExecutionException ee -> ...ee...
case throws InterruptedException ie -> ...ie...
}
```