Remix.run Logo
seunosewa 2 days ago

Getting rid of checked exceptions would be very beneficial for Java's popularity. No modern JVM language has kept that annoying feature. Just stop checking them at compile time, or make it an optional feature.

Defletter 2 days ago | parent | next [-]

Checked exceptions are not the problem. In fact, I don't think there should be ANY unchecked exceptions outside of OOM and other JVM exceptions. I will die on this hill. The actual problem is that handling exceptions in Java is obnoxious and verbose. I cannot stress enough how bad it feels going from Zig back to Java, error-handling wise. If Java had Zig-style inline catching, there wouldn't nearly be as much humming and hawing over checked exceptions.

xmodem 2 days ago | parent [-]

A hot take I have is that Java's checked exceptions - which everyone hates - are semantically very similar to Rust's Result<T, E> and ? error handling - which is broadly liked. The syntax makes or breaks the feature.

lock1 a day ago | parent | next [-]

It shares the same important property (short-circuiting, explicit & type checkable) with Rust's `Result<>`, yes. I don't think it's a direct equivalent though

- 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.

redditor98654 a day ago | parent | prev [-]

A while ago, I had attempted to do it the Rust style with a Result type in Java (where it is called Data oriented programming) and the result is not great. I was fighting a losing battle in a naive attempt to replace checked exceptions but still retain compile time type safety.

https://www.reddit.com/r/java/s/AbjDEo6orq

lock1 a day ago | parent | next [-]

Interesting anecdote.

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"
Defletter a day ago | parent | prev [-]

Yeah, Java's generic-type erasure makes result types difficult, though as you mention in your code example, this can be mitigated some using switch guards. But you could also go into another switch:

    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
            }
        }
    }
elric 2 days ago | parent | prev | next [-]

I disagree entirely, but I'd love to hear your arguments (other than vague allusions to "popularity" and other languages).

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.

peterashford a day ago | parent [-]

I'm 100% with you - Streams is the one area where I think they dont pay well. Everywhere else, they're fine

seunosewa a day ago | parent [-]

Which checked exceptions do you declare or enjoy having to handle in your codebase?

peterashford 18 hours ago | parent [-]

I declare any which I'm not handling at the point they're raised. Unless I'm just ignoring them in which case I just declare methods throw Exception.

PhilipRoman 2 days ago | parent | prev | next [-]

I think checked exceptions are fine, they just haven't updated them along with other features. We now have expression based switch, why not have expression based try-catch? The other major problem is inability to pass them through the new functional interfaces, since they usually don't have anything like `<X> throws X`. But this could be solved by an Optional-like type.

HexDecOctBin 2 days ago | parent | prev | next [-]

(I am primarily a C developer)

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.

xmodem 2 days ago | parent | next [-]

Part of it is an ecosystem thing. It's a lot better now, but there were times when libraries would throw a variety of checked exceptions that usually couldn't be handled sensibly other than by being caught and logged by your top-level exception handler. This forced you to either

(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.

lock1 2 days ago | parent | prev | next [-]

The only way to handle a checked exception is either by declaring `throws` in the function signature to propagate it, or by using annoying try-catch statement. Then, due to the verbosity of try-catch, some decided to misuse it with an empty catch block.

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.
ahoka 2 days ago | parent | prev | next [-]

In practice there are problems: Everything can throw IOException technically which you need to handle. There are non checked exception anyway, so there might be exception that are thrown anyway. It's a mess and there is a reason no other mainstream language uses them, even if Java people don't like that fact.

zmmmmm 21 hours ago | parent | next [-]

A lot of that comes back to the lack of power in the type system (type erasure, lack of union types, etc).

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.

peterashford a day ago | parent | prev [-]

"Everything can throw IOException technically which you need to handle"

wut?

watwut a day ago | parent | prev [-]

Imo, some standard libraries were massively overusing them. So, you needed to handle checked exceptions for stuff that you cant do much about or are highly improbable and genuinely should be runtime exceptions.

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.

peterashford a day ago | parent [-]

See, I really don't get this. If you really don't care, just put "throws exception" on your methods and be done with. You can start lazy and tighten up if you want - your choice

blandflakes a day ago | parent [-]

This really becomes tedious when you're working with Java's primary means of codesharing/abstraction, inheritance. You can't subtract an IOException from an interface, and you can't introduce one if you're extending an interface. So you're forced to handle an exception that you frequently can't recover from and that paradoxically you probably don't expect to ever be thrown, for a majority of methods.

zmmmmm 2 days ago | parent | prev | next [-]

it's is kind of interesting that we are in a phase where Rust is the "cool" language even though it has a similar kind of feature. I wonder which way it will age.

peterashford 2 days ago | parent | prev | next [-]

Obviously YMMV, but I like checked exceptions

forinti a day ago | parent | prev | next [-]

I would like some sort of pragma that would allow me to ignore all checked exceptions in a file. Something like "use nocheck;" and then all methods would implicitly throw Exception.

Sometimes I want to write a small program for a one-off task and handling Exceptions is absolutely unnecessary.

DarkNova6 a day ago | parent | prev | next [-]

This is a typical opinion to have. This is also an uninformed opinion.

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...
} ```
smrtinsert a day ago | parent | prev [-]

There should be a middle ground where the IDE could generate your exceptions for you based on unchecked exceptions. Dunno what to call that but dealing with checked exceptions in the IDE is so much easier than manually reading docs for runtimes. Yes you could vibe + c7 it these days, but still the whole thing feels nicer with checked exceptions.