top | item 36708759

Unchecked Java: Say goodbye to checked exceptions

143 points| rogerkeays | 2 years ago |github.com

291 comments

order

breadwinner|2 years ago

One of the biggest flaws in C#, in my experience, is lack of checked exceptions. As an example, I wrote some very good code, carefully tested it, made it work flawlessly, then suddenly it started crashing. What happened? Someone made a change in a function I was calling, and it started throwing a new exception. This would have caused a compile error in Java, not a crash.

More on checked vs unchecked exceptions here: https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dl...

jabiko|2 years ago

Lets be honest. The more likely thing is that either the coworker would use an unchecked exception or that they would change the callsite to:

  try {
    theUpdatedFunction();
  } catch (MyNewCheckedException e) {
    logger.warn("Whoopsie doopsie", e)
    throw new SomeUncheckedException("Something failed, idk", e)
  }
Which really is a zero sum game. The code still breaks the same way, but the checked exception gets eventually wrapped in an unchecked one. We still would have the situation that someone changed the behavior of the function in a way that is incompatible with your usage.

That bug should have been caught by tests, code reviews and good communication.

wvenable|2 years ago

> One of the biggest flaws in C#, in my experience, is lack of checked exceptions.

I couldn't disagree more. Checked exceptions in Java have ruined a generation of programmers.

The truth is, under checked exceptions, to satisfy the compiler the function that you called would declare that it throws a SomeModuleException and the programmer who wrote that function would put all his code in try/catch block that catches all errors and rethrows a SomeModuleException with the real exception passed as the cause.

Checked exceptions just don't work.

__jem|2 years ago

On the other hand, in lots of application code, there's often not much to do other than crash or abort the request, and this can often be safer than trying to proceed along code paths that are almost always under-tested. We've all seen Java code where a junior has decided to swallow an error without much thought. This obviously isn't true for every use case, but failing fast is often a pretty good policy.

h4x0rr|2 years ago

Checked exceptions just always felt annoying to work with. Imo Rust's Result type is more versatile

taeric|2 years ago

I mean, maybe? Fun degenerate cases to consider: Someone throws a Environment.Exit(0) into a random library you are using, instant pain. Someone throws an infinite loop into a library you are using, similar instant pain.

There is no magic language trick that can prevent you from having to rerun all tests for your software if you update a dependency. Pretty much period.

(I say this as someone that isn't really opposed to checked exceptions.)

nradov|2 years ago

The trouble with checked exceptions is that they prevent you from easily extending classes or implementing interfaces that you don't control. Your new class might need to throw a checked exception not included in the method signature. So then you have to resort to hacks like wrapping the new checked exception inside a runtime exception.

sonicgear1|2 years ago

You should catch any non specific exception in the last catch block, just in case. Thought that was the standard. Java gets very messy in mixing checked and unchecked exceptions, I've seen a lot of devs just ignore the unchecked ones.

skissane|2 years ago

> Someone made a change in a function I was calling, and it started throwing a new exception. This would have caused a compile error in Java, not a crash.

Not necessarily. If it started throwing a new RuntimeException, it wouldn’t have. There are also sneaky ways to throw checked exceptions without declaring them, for example using Lombok’s @SneakyThrows annotation

oh_sigh|2 years ago

Your code might have been very good, but it wasn't future proof. It sounds like your code would have been fine if you locked down the libraries you were calling to a fixed version or something.

29athrowaway|2 years ago

In Rust instead of throwing, you have results that can be either successful or errors. And it is all type safe and fast.

joiqj|2 years ago

One of the reasons I hate exceptions overall. Someone may add new ones, they break the control flow, they bubble up all over the place, etc etc. They are basically indomitable.

bedobi|2 years ago

Exception based error handling is so bad and unsafe that adopting functional error handling with Either, Try etc as implemented by functional addon libraries for many languages, while not yet common, in time it will become the new default even in OO languages. (just like it's been the default in functional languages for decades)

Functional error handling types are much simpler, safer and more powerful.

Simpler because they don't rely on dedicated syntax- they're just regular objects no different to any other object.

Safer because unlike exceptions, they force callers to handle all potential outcomes, but no more. (no risk of ignoring errors and no risk of catching a higher level of error than desired, ubiquitous bugs in exception based error handling)

Powerful because they support map, flatmap, applicative etc, making it easy to eg chain multiple computations together in desired ways, which is unwieldy and bug prone when using exceptions.

> What is wrong about dedicated syntax

It adds complexity to the language! It could be that, when learning Java, Kotlin and any other language, we learn that methods return what they say they do... and that's that. No weird dedicated syntax and magic, special treatment for returning anything other than the happy path, and the HUGE complexity that comes with it, eg the dedicated syntax itself and how it behaves, differences between checked and unchecked exceptions, hierarchies of exceptions etc etc.

> Exceptions are easier

But that's the point, they're not.

Exceptions based error handling is unnecessary, hugely complex, doesn't compose at all, obfuscates or straight up hides what can go wrong with any given call, so leads to countless trivially preventable bugs... I could go on. And after decades of use, there's still no consensus about what exceptions should be or how they should be used. Exceptions are a failed experiment and I have no doubt that in ten years, Java, Kotlin and many other languages will acknowledge as much and move away from it the same way Joda Time outcompeted and replaced the horrible Java date and time library.

kaba0|2 years ago

Checked exceptions are exactly analogous of Result/Either types. They are just built into the language with syntactic sugar, automatically unwrap by default (the most common operation), can be handled on as narrow or wide scope as needed (try-catch blocks), does the correct thing by default (bubbling up), and stores stack traces!

In my book, if anything, they are much much better! Unfortunately they don’t have a flawless implementation, but hopefully languages with first-class effects will change that.

misja111|2 years ago

Exception based error handling is unsafe when they are unchecked exceptions. Checked exceptions however are as safe as Either, Try, Monads, Applicatives or whatever. You are forced to declare them in your method signature, the caller is forced to either handle them or rethrow them + declare them as well. And I guess this is precisely why so many developers hate them; they don't like the extra work they have to do to catch all those edge conditions. This is why we see so many empty catch blocks or upcasting to Exception or even Throwable; it is laziness.

I would also argue that checked exceptions are no more complex than Eithers, Try or Applicatives. Actually passing Eithers or Applicatives around everywhere can easily clutter your code as well, IMO it can be worse than checked Exceptions.

The_Colonel|2 years ago

> Safer because unlike exceptions, they force callers to handle all potential outcomes, but no more.

Which is bad. In 99% of cases I have no specific error handling for the given problem. Just let the process crash or be handled by application server / framework.

Representing these kinds of error conditions in the code which I have no interest in handling is just noise.

spion|2 years ago

Exceptions are not unsafe.

That said, not modelling them in the type system is a mistake. But the model has to be useful - knowing what functions can or cannot throw is useful, knowing what they throw, less so.

(They are safe because of try-finally / try-with-resources)

wvenable|2 years ago

> Safer because unlike exceptions, they force callers to handle all potential outcomes, but no more.

That's what wrong with them. Callers, in almost all cases, do not know what to do exceptional outcomes. So you force programmers into a situation where they're dealing with things they shouldn't be dealing with.

asddubs|2 years ago

You mean something like C++ Optional, right? Those are nice, if the language supports generics.

asddubs|2 years ago

I actually quite like checked exceptions, and miss them in other languages. My biggest gripe with exceptions is that a few calls deep, you can no longer tell whether calling something might throw or not, and what type of exception it might potentially throw.

_old_dude_|2 years ago

Checked exceptions are a leaky concept in Java. The Java type system has no union types so you can not have a generics method that abstract more than one exception.

That's why Stream::map can not capture the checked exceptions properly.

josephcsible|2 years ago

I like checked exceptions in principle too, but Java's implementation of them is so bad that I hate them there. The first major mistake is that it doesn't support exception polymorphism (e.g., I wish you could pass a comparator to Arrays.sort that throws checked exceptions, and have that call to Arrays.sort itself then throw the same checked exceptions), and the second is that the standard library makes a bunch of exceptions checked that should be unchecked (e.g., IOException from close()).

nepthar|2 years ago

Reading through the comments, I realize this may be a minority view - but I like coding for the happy path and letting exceptional states crash. I find languages like go a bit harder to parse quickly because I always have to "unwrap" the happy path from all of the mixed in error handling.

I'm sure I'd get used to it eventually, but I like that unchecked exceptions in Java are now an option!

Delk|2 years ago

It really depends on what you're developing.

If it's some random web backend, it's often fine to just let the error propagate as a 5xx. Many error cases wouldn't have a better solution anyway, and all that's breaking is a single page or resource load. (Of course on the frontend it might be nicer to show some kind of an error than just e.g. having frontend functionality silently fail.)

If it's a desktop application and you're crashing the entire application because wifi happened to drop or whenever there's anything your happy path didn't predict, that's going to be bad.

If it's some kind of an embedded thing and you're crashing an entire device because you didn't bother to handle cases that are outside of the happy path but entirely possible, I only hope it's not something important you're developing.

asddubs|2 years ago

I'm fine with doing this on purpose, but without a system like checked exceptions, you do it without even really realizing you're doing it. Checked exceptions point out the errors and then let you decide whether it's something you should handle or let it crash. It makes for more stable software.

ziml77|2 years ago

If you actually let it crash that's fine, but a lot of people just toss a catch (Exception ex) in there because they don't know what exceptions will happen and don't want errors that aren't actually a big deal to bring the system down. But the issue with catching everything, even if you log before continuing, is that the process may be in a bad state and you're just continuing with it like that. I got to see a couple database tables get absolutely mangled by a process that had an exception that was caught and logged. That was a pain to clean up since doing nothing more than restoring a backup would have lost the entire day's worth of business operations in that system.

EspressoGPT|2 years ago

This is what I like about Go and other more recent languages: Especially when networking or other IO is involved, things will go wrong. These languages don't try to circumvent this reality but instead embrace it by treating errors as very-first-class citizens. My error handling tends to be more thoughtful in those languages than in, say, Java.

the_gipsy|2 years ago

In practice there is a lot of paths that you don't want to "crash" on, but try the next thing etc.

raincole|2 years ago

> I like coding for the happy path and letting exceptional states crash.

Of course everyone like coding like this. No one likes to code error handling code.

Just like no one likes to code tests and documentations.

It turns out not every important thing is joyful.

clownvorld|2 years ago

The Trouble with Checked Exceptions A Conversation with Anders Hejlsberg, Part II

https://www.artima.com/articles/the-trouble-with-checked-exc...

taftster|2 years ago

This is an insightful interview, thank you for the link. I'm well read up on the topic, but this interview was still great and is a good perspective on the checked/unchecked debate.

The fact that Java has introduced UncheckedIOException, in my opinion, shows how some people in the Java community have come to believe that checked exceptions were a mistake (understanding that lambda forced the issue). There's probably not too much to be easily done at this point, but consideration for changing checked exceptions in the JDK to extend RuntimeException sure would be interesting.

nnnnico|2 years ago

Allowing unchecked exceptions in languages without explicit error handling or the return of error values is a mistake IMO! Makes it impossible to call a function safely

RGBCube|2 years ago

Exactly, C++ exceptions are horrible, you never know what throws what. Java made C++ exceptions better by making them explicit, so you always know what throws. Then Kotlin came and made everything a unusable mess (don't get me wrong, I love Kotlin, just hate that it doesn't have explicit exceptions).

I honestly think the best error handling strategy is employed by Zig, then Rust. they're very explicit while not getting in the way, you always know what throws what and what doesn't.

Rust has a little issue though, which is that people can make their functions return `Result<T, Box<dyn Error>>` which makes handling the returned error difficult, Zig does not have any of that.

_old_dude_|2 years ago

In Java, no method call is safe (Haskell/Rust safe), the langage will hapilly throws a null pointer exception or an out of memory error.

But I don't think C#, Kotlin or Scala are less safe than Java even if they do not have the concept of checked exceptions.

rho4|2 years ago

I would argue the opposite: Junior and senior developers alike catch and swallow or log checked exceptions all over the place, making it impossible to know if your method call was successful or not.

wvenable|2 years ago

What does it mean to call a function safely?

I have a desktop application with a single exception handler wrapping it's event loop. The handler display the message to the user and returns the to loop. It's rock solid -- nothing can crash it. The user can try to save their file to some network share that just disappeared and when that fails just save again somewhere else.

But there is no such thing a safe function to call. Whether you handle errors by meticulously passing them around everywhere or not makes little difference.

ars|2 years ago

Java just needs one small change to fix exceptions.

Instead of the exception type being checked or unchecked, the throw should specify checked or unchecked.

So for example the first time the exception happens it can throw a checked exception for the caller to deal with.

If the caller doesn't deal with it it can simply throw its way all the way to the top without every single function needing to declare they handle it.

nnnnico|2 years ago

(To clarify, i'm against exception based error handling, but removing checked exceptions in languages without other mechanisms of explicit error handling makes things more brittle. For example in kotlin, where some Java practices to throw all over the place are still in use, unchecked exceptions makes things worse. You wont know if a function will throw unless you inspect the code or trust it's documentation)

w10-1|2 years ago

The friction between the benefits of detecting errors statically, handling them quickly, and the diversity of needs for error handling cannot be avoided with any fixed policy like "unchecked only".

This tool does remind us that checked exceptions in Java are a compile-time phenomenon only, so (non-compliant) compilers can be made to ignore them. Neat, but... helpful? It would certainly lock you into using the tool.

Instead, we need configurable policies for error handling, where the same code can be statically analyzed much more deeply (e.g., to include unchecked exceptions) or run more leniently (e.g., when exceptions are used only for flow control). Some of this might pertain to local declarations, and some might be policies of various scopes (per VM, per-package, or per call-chain), and would probably fold in assertions and fibers/virtual threads and some OS-sensitive behaviors. The goal would be to write the code once (with full disclosure, i.e., only check-able exceptions and assertions), but to reuse the same code under different policies.

Whether anyone wants to do the work of folding this into the VM as security privileges were is another question...

smrtinsert|2 years ago

Agreed. I absolutely prefer checked exceptions since it forces devs and consequently business to develop requirements for possibilities. Lack of ambiguity helps me sleep at night. Potential runtime errors do not.

theanonymousone|2 years ago

Putting aside the discussions about the necessity of checked exceptions (to which I feel unqualified to express my opinion), I believe the approach used by the OP article to remove them is not the best one:

- It requires modifying compiler arguments and putting some file in the local classpath! That's really not Java-ic. Is it?

- It is not transparent in the code. You cannot infer by looking at the source code that something has changed.

Since some time ago I use the NoException library[0] in my Java projects which achieves the same goal but without the above-mentioned issues. It can also be used to mimic Scala's Try construct.

[0] https://github.com/robertvazan/noexception

dtech|2 years ago

Most other languages agree that checked exceptions are not good by not having them.

As for alternatives, Try/Result and similar monads have decent adoption even in Java, but personally I quite like the Kotlin philosophy [1] to not have generic error containers and either use runtime exceptions for errors that cannot be reasonably handled by the caller and encapsulate failures in the return type if they can.

[1] https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/...

earthboundkid|2 years ago

Conceptually there’s not much difference between checked exceptions and multiple returns or returning a tuple with an error etc. The problem with Java is that the checked exceptions are too fine grained, so the signatures are brittle and non-composable. Everything should just throw an Exception and then use dynamic typing to figure the rest out, and then you wouldn’t have the problem that your toList method can’t compose with something that throws an unknown Exception subclass.

phoe-krk|2 years ago

> Unchecked does not make any changes to your bytecode. This is possible because the JVM does not know about checked exceptions. It's been the compiler holding you back all this time.

TIL!

galaxyLogic|2 years ago

This seems like a major improvement for Java readability as shown by the examples.

What I don't like is the examples of having to use Maven or Gradle to install it. Why does that have to be so verbose, and something written in another language?

rogerkeays|2 years ago

Ha, yeh. At first I had the command line examples first, but everyone was going on and on about maven and stuff, so I figured that's what the audience wants.

[EDIT] I moved the command line stuff back to the top of the README. XML makes my eyes bleed too...

briffid|2 years ago

Checked exceptions turn out to be part of the API, meanwhile violating Liskov substitution principle and preventing some design patterns. This is because a superclass implementation might throw a checked exception, then its subclass not necessarily. However, if the caller refers the subclass instance via the superclass interface it must catch the exception, but if it refers via the subclass it must not catch, as that results in a compile error. So the point is that with checked exceptions you cannot replace an interface with any implementation, which should be possible in proper OO programming.

Hackbraten|2 years ago

That’s not a Liskov violation. Changing the compile-time type of a local variable in the scope of the call site is not substituting the implementation of the referred-to object. Those are two different things.

As you correctly mentioned, if you leave the compile-time type of a variable unchanged, then assign a different object (one that belongs to a subclass that doesn’t throw the exception when you call its method) to that variable, and then call that same method, the compiler still forces you to catch. That’s the Liskov substitution.

throwaway2037|2 years ago

I never forget reading the javadoc for Google Gson class com.google.gson.JsonParseException (extends RuntimeException):

    This exception is a {@link RuntimeException} because it is exposed to the client. Using a {@link RuntimeException} avoids bad coding practices on the client side where they catch the exception and do nothing. It is often the case that you want to blow up if there is a parsing error (i.e. often clients do not know how to recover from a {@link JsonParseException}.
I have *completely* the opposite style: Throw checked for anything that can throw, except: null pointers and invalid arguments (e.g., negative port number). Why so much checked? The caller cannot hide from it. They are forced to ack it, else compiler error. At the very worst, they are free to rethrow as a wrapped RuntimeException (ugh).

When I write code in C#, I always left wondering: Does this method throw an exception (ignoring null pointer and invalid arguments)? It bothers me. And, I write that as someone who really likes C# -- it's a great language for my needs.

And, yes, I can appreciate that many people don't like the friction associated with checked exceptions. It does add a lot of visual bloat! Serious question: What are the alternatives that _communicate sufficiently_ through an interface? Honestly, I struggle to think of a better alternative, so I just live with it.

Just like the bad old days of integer return values used to indicate error state (C-like languages), you need to check every single call -- there are no shortcuts, especially with I/O. For me, it is similar with Java and C# when writing I/O code: You need to catch and correctly handle every (checked-ish) exception.

exabrial|2 years ago

I love the idea of this project. Just needs some IDE and possibly some verifier support!

I do just want to correct a tiny piece of the readme:

> a common practise is to rethrow it as a RuntimeException. The problem with this, ... is that the root cause of exceptions get hidden,

This is most definitely not true. If you wrap / re-throw as a RuntimeException you will absolutely get your original stack trace.

edpichler|2 years ago

This dependency removes one of the best features of Java, which make others think about what happens if things go wrong.

code_runner|2 years ago

only the things that someone acknowledges may go wrong

lucasyvas|2 years ago

This is a mistake - checked exceptions and functional equivalents are the clear path forward.

watwut|2 years ago

Nothing better then having to guess whether the method may throw an exception or not.

dtech|2 years ago

You already have to guess. All method calls van throw Runtime exceptions.

code_runner|2 years ago

nothing better than someone assuming a method can only throw a specific type of exception or won't throw at all.

vbezhenar|2 years ago

Checked exceptions are one of biggest mistakes Java ever made.

That said, patching compiler is not something I would do.

What I usually do is add unchecked exception class for every checked exception I had to "handle". So I have UncheckedIOException from standard library (handy!), UncheckedSQLException and so on and so on.

Yes, it causes more code, but Java is not exactly known for brevity, so be it. At least that way is not hacky in any way and actually used by Java standard library itself, so I'd consider it "idiomatic".

rogerkeays|2 years ago

You can also rethrow checked exceptions as runtime exceptions by exploiting type erasure:

    public static RuntimeException unchecked(Exception e) {
        Exceptions.<RuntimeException>throw_checked(e); 
        return null; 
    }
    private static <E extends Exception> void throw_checked(Exception e) throws E {
        throw (E) e;
    }
then you can do this:

    try { ...
    } catch (IOException e) {
        throw unchecked(e);
    }
And the original exception is thrown without being wrapped. It's as though you had `throws IOException` on your method signature.

This is from my original solution to the problem: https://github.com/rogerkeays/jamaica-core/blob/0cc98b114998...

flerchin|2 years ago

Neat. Reminds me of lombok in that it really only affects compile-time and makes for cleaner code in a way that many (but not all) developers would want.

jameslars|2 years ago

Ugh Lombok! Literally everything it does is replaced by any competent IDE with auto-generated methods, with the added benefit of not requiring special build handling steps because the library can't play by the normal annotation processing rules.

There was maybe a time Lombok made sense. It does not anymore. Death to Lombok.

alanfranz|2 years ago

I don't want to remove all checked exceptions in random code, btw. Even though they're not common, it can be bad if they're unhandled in some cases. How can I know beforehand?

I'd like something to soften some of them, probably in a configurable way, in some builtin interfaces btw. E.g. many IOExceptions should really be unchecked.

javajosh|2 years ago

Like the idea, but I probably won't use it. Yes, checked exceptions are annoying, but the correct place to fix them is the source. This compiler plugin blinds you. The ordinary ways to mitigate checked exceptions (wrapping them in unchecked exceptions) is ugly but its also explicit.

Note: I will try it, because maybe I'm wrong.

rogerkeays|2 years ago

You still get warnings about checked exceptions from this plugin, so it's not entirely flying blind.

code_runner|2 years ago

To the credit of the die-hard Java community, you all really seem to love and support even the most horrific syntax and awful language features. You love the pain. The rest of us are here for you... I promise software can be fun.

Checked exceptions are far and away the worst part of java and I'm glad that no other language I've personally encountered have them!

On a more real note, I work at a shop with a fair amount of java now and checked exceptions are definitely my biggest complaint. I hope we adopt this, I'm sure we won't, but I'm glad to see a little movement on what I feel is the crusty status quo in the java world.

bottlepalm|2 years ago

Agreed, checked exceptions are awful. The idealists love them, the realest knows you can't write code up front handle every single exception case, and in most cases you aren't recovering from an exception anyways.

Especially the higher the level the code is the more potential lower layers that can fail. It's just not scalable to write code up front to deal with every single case. And most programmers don't care about failure, or working around it, just success.

On the other hand we do expect the unexpected and wrap failure prone code in try/catch so that we can log exceptions and keep the app moving. Figure out later if the ROI is there from enough failures to write special code to try to prevent and/or recover from a particular exception.

It's pretty much the same as it is in the checked exception world just with more code - in Java people handle the exception, log it and/or rethrow, so what's the point? Idealism and poor assumptions by the language designer. Anyone have any proof that a Java app is more reliable than a C# one?

edpichler|2 years ago

Later you detect an error, more expensive it is to fix it.

ars|2 years ago

That's not really true.

Vast majority of the time either the caller will fix it, or it simply does not need to be fixed at all it's simply passed all the way up to the top.

invalidname|2 years ago

FYI this too is already supported by Manifold...

_ea1k|2 years ago

That is referenced in the readme. :) TBH, I hadn't seen either one before today, and they both look really nice.

The type-safe JSON feature in Manifold could be incredibly useful.

dgellow|2 years ago

What is manifold in this context?

_ZeD_|2 years ago

please, for the love of what's good in the world... NO. checked exceptions ARE A FEATURE, a CORE ONE.

dtech|2 years ago

They are so bad even Java stdlib stopped using them for the most part. Nearly all Java 8+ APIs like streams use unchecked exceptions.

lesuorac|2 years ago

There's a reason using streams with checked exceptions is so painful; checked exceptions are going the way of the dodo.

arein3|2 years ago

In kotlin there are no checked exceptions, and I haven't missed them a bit.

macpete42|2 years ago

catch them at the source and return sealed classes (used like sum types) instead