More on checked vs unchecked exceptions here: https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dl...
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.
> You see programmers picking up new APIs that have all these throws clauses, and then you see how convoluted their code gets, and you realize the checked exceptions aren't helping them any.
And he goes on to elaborate on how this gets more complicated when versioning is introduced into the mix.
There's a reason we have compile-time checks. If you think compile-time checks should be replaced with additional tests, code reviews and good communication, then you want a scripting language, not a compiled language.
You do not wrap checked exceptions in an unchecked one... unless you are a really bad Java programmer.
try {
theUpdatedFunction();
} catch (MyNewCheckedException e) {
logger.warn("Whoopsie doopsie", e);
throw new SomeUncheckedException(e.message);
}
There's a special place in Hell for these people, but at least they'll get to see all of their friends again.That's not a valid argument. This is an explicit decision, so they reap what they sowed.
One could just as well make the same argument for Optionals, that you can just do:
let val = foo.unwrap();This is such an unproductive cop out response. The problem could be completely solved by functional error handling, which leverages the compiler to force you to handle exactly the errors than can happen, no more, no less.
Who does that? It looks like an anti-pattern.
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.
I have seen libraries that make network calls that do the following:
* Throw an exception on certain types of network errors * Return an HTTP error code for other types of network errors * Return an object with error set to true and an error string for other types of network errors
NONE of those was documented, it is crazy. This actually bite me in prod when an undocumented exception that we hadn't seen in over a year of uptime was finally thrown. I had to look at network logs to try and figure out why it was being thrown (no checked exceptions in JS!), which itself is absurd.
https://learn.microsoft.com/en-us/dotnet/api/system.exceptio...
However in small to mid sized enterprise software companies with average developer talent it’s important to keep boundaries (and blame) clear.
In the scenario in question, the CTO will blame OP and make them work the weekend to diagnose/fix it, so wrapping the other exception and throwing their own SomeModuleException will cover their ass.
Java is popular for all the wrong reasons. Believe it or not there are hundreds of such companies in the US alone heavily using Java in this manner and then there is the whole offshore development segment.
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.)
Of course, but having a checked exception (even better, having the errors be part of the return type via Result<T, E> or Option<T>) solved an entire class of problem. Doesn't mean that there aren't others of course. But surely this is a win?
>inb4 it makes the code very complex with nested return type Result<Result<Result<T, FileNotFoundError>,ReadError>, ParseError>
Then it was just hidden from you before. The complexity had always been there, it just never occurred to you that it can happen.
Hopefully, your answer is compile time. If so can you now understand why you would want to be alerted about a new exception getting thrown at compile-time as well?
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
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.
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.
No, they aren't.
They are not compositional. You may want to write `f(g())` but there's no way to write the parameter type of `f` to make this work (in Java). That's because checked exceptions are an "effect" that would require extending the Java type system.
In languages that rely on Result/Either for error handling, you've got two types of errors: Typed errors (Result/Either) and untyped panics. Typed errors are supposed to be handled, possibly based on their type, while panics can be recovered from ("catched") but these are serious, unexpected errors and you're not supposed to try to handle them based on their type. Since typed errors generally need to be handled explicitly while untyped errors are unexpected, typed errors are always checked (you can't skip handling them), while untyped errors are unchecked (implicitly propagated up the stack if you don't do anything to catch them).
Java has three types of errors:
1. Checked errors, a.k.a. checked exceptions: (exceptions that inherit from Exception, but not from RuntimeException). 2. Unchecked application errors: exceptions that inherit from RuntimeException. 3. Unchecked fatal errors: exceptions that inherit from Error.
These three kinds of errors live in a confusing class hierarchy, with Throwable covering all of them and unchecked application errors being a special case of checked application errors.
Like everything else designed in the early Java days, it shows an unhealthy obsession with deep class hierarchies (and gratuitous mutability, check out initCause()!). And this is what destroyed the utility of checked exceptions in Java in my opinion.
Consider the following example: We have a purchase() function which can return one of the following errors:
- InsufficientAccountBalance - InvalidPaymentMethod - TransactionBlocked - ServerError - etc.
You want to handle InsufficientAccountBalance by automatically topping up the user's balance if they have auto top-up configured, so you're going to have to catch this error, while letting the rest of the errors propagate up the stack, so an error message could be displayed to the user.
In Rust, you would do something like this:
account.purchase(request).map_err(|err| match err {
PurchaseError.InsufficientAccountBalance(available, required) => {
account.auto_top_up(required - available)?
account.purchase(request)
}
_ => err // Do not handle other error, just let them propagate
})
In Java, you would generally do the following: try {
account.purchase(request);
} catch (InsufficientAccountBalance e) {
account.auto_top_up(e.requiredAmount - e.availableAmount);
account.purchase(request);
} catch (Exception e) {
// We need to catch and wrap all other checked exception types here
// or the compiler would fail
throw new WrappedPurchaseException(e);
}
The "catch (Exception e)" clause doesn't just catch checked exceptions now - it catches every type of exception, and it has to wrap it in another type! Of course, you can also specify every kind of checked exception explicitly, but this is way too tedious and what you get in practice is that most code will just catch a generic Exception (or worse - Throwable!) and wrap that exception or handle it the same way, regardless if it was a NullPointerException caused by a bug in code, an invalid credit card number.The worst problem of all is that once developers get used to write "catch (Exception e)" everywhere, they start doubting the values of checked exceptions: after all, most of their try clauses seem to have a generic "catch (Exception e)", so does it really matter at all of they're using checked exceptions?
This is the reality. Checked exceptions failed in Java. Most Java developers see them as nothing more than a nuisance and look for ways to bypass them. That does not necessarily mean that the concept of checked exception as a language level facility for errors has failed, but it certainly failed the way it has been implemented in Java.
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.
Good developers should be lazy. Checked exceptions require you to do a bunch of cumbersome ceremony that makes your code unreadable, for what should be (and is, with Either or equivalent) at most a couple of symbols; no wonder developers hate that.
> I would also argue that checked exceptions are no more complex than Eithers, Try or Applicatives.
You'd be wrong. Checked exceptions as implemented in Java require two keywords of their own (throw and catch), a special change to method signatures (throws), a unique new kind of expression for multi-catch (|), a unique new kind of type for things caught in multi-catches, and special support in every other new language feature (e.g. Futures have a whole bunch of extra code to deal with exceptions). Eithers can literally be a regular class that you could write yourself using ordinary language features. A small piece of syntax sugar (something like "do notation", "for/yield comprehensions", or "?") is a good idea, but not essential, and if you do it right then you can implement that once across your whole language (for applicatives/monads in general) and use it for eithers, futures, database transactions, audit logs, almost anything. https://philipnilsson.github.io/Badness10k/escaping-hell-wit... .
try {
throws_a();
r = grab_resource();
throws_b();
r.throws_a();
} catch (a) {
r.release(); // oops; null pointer some times
} catch (b) {
try {
r.release();
} catch (a) {
// nooo....
}
} finally {
if r != null {
r.finalize() // use after release (sometimes)
r.release() // ???
}
}
There is plenty of academic literature showing that real programs are even worse than my contrived example, on average.And ones that are so unergonomic that only industrious developers will get them right will result in worse software.
Modern languages allow these to be zero cost abstractions so there is little tradeoff.
I hate checked exceptions when they force me to handle an exception which I know is impossible given the arguments passed to the method. For example, some older Java APIs take an encoding name and force you to handle the checked UnsupportedEncodingException - even when the encoding name was something like “US-ASCII” or “UTF-8” which is required to exist by the Java standard, and if somehow they didn’t there is often no possible error recovery than just crashing. This has been fixed in newer versions by introducing new APIs which throw an unchecked exception instead, and also by introducing constant objects for the standard charsets
No. If I add a checked UserNotFound exception to a getUser db call, you can bet someone higher up the stack will do try catch Exception e, so now they're catching OutOfMemory and who knows what else.
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.
> I have no specific error handling for the given problem
then pass it on and decide what to do with it at the system boundary
in the db:
fun getUser(uuid: UUID) : Either<Error, User>
middle layers: pass around the either, you can map, flatmap etc on it to chain it with other computations there
then in the resource layer
return user .fold({ error -> when(error) { is DbError -> HttpResponse.InternalServerError() is UserNotFoundError -> HttpResponse.NotFound } }, { user -> HttpResponse.ok(user) })
Then, at every layer, each method explicitly says in the method signature what it returns. There’s no need to look around each and every line of every method in every layer, or in the framework, or some global exception handler, to figure out what will happen. Developers can tell from a glance at the resource method what the endpoint will return for each outcome, in context.
Things like being unable to communicate with the DB or not finding a user are not exceptional, they're entirely expectable when you're calling a db to look for a user and should be modeled accordingly.
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)
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.
That's why Stream::map can not capture the checked exceptions properly.
I'm sure I'd get used to it eventually, but I like that unchecked exceptions in Java are now an option!
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.
What and where you can handle exceptions has nothing to do with where the exception is thrown. Handlers only exist at key points in the application at the start of operations where you can skip, abort, or retry.
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.
https://www.artima.com/articles/the-trouble-with-checked-exc...
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.
https://github.com/rogerkeays/unchecked/blob/f22c8cde3557de0...
No need to change the type hierarchy. Just make it a compiler option.
https://github.com/manifold-systems/manifold/tree/master/man...
With an article having the same name as your post:
https://github.com/manifold-systems/manifold/blob/master/doc...
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.
Take for example if you call to an OS API and it returns an error that wasn't documented to be returned from that function. You can't return that code up the callstack using Zig's error mechanism. Instead there's functions such as unexpectedErrno and unexpectedError. Those call the appropriate method to get a string representation of the error and call std.debug.print to display it, then they just return error.Unexpected.
That means that the caller doesn't have any control over how the error is displayed. Meanwhile in Rust, you can add an UnexpectedError variant to your enum and let it carry an error code with it. The caller can then display that error however they want.
I don't hate Zig's error handling by any means, but personally I think Rust does it better. I'm happy to see that C++ seems to be going with the Rust way by adding a Result-like type std::expected.
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.
>Makes it impossible to call a function safely ?? catch(Exception e) anyone?
[1] https://literatejava.com/exceptions/checked-exceptions-javas...
[0] https://www.yegor256.com/2015/07/28/checked-vs-unchecked-exc...
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.
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.
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...
- 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.
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/...
TIL!
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?
[EDIT] I moved the command line stuff back to the top of the README. XML makes my eyes bleed too...
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.
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.
Also, without IDE support such a plugin will never gain a lot of traction.
https://github.com/manifold-systems/manifold/tree/master/man...
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.
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".
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...
So yes, you can throw IOException masking it to RuntimeException, however you can't catch it later without another gimmicks like catching Exception and checking if it's IOException (and even that check causes linter warnings in Idea, so now you need to suppress those warnings...).
Throwing and catching UncheckedIOException does not have this problem.
There was maybe a time Lombok made sense. It does not anymore. Death to Lombok.
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.
Note: I will try it, because maybe I'm wrong.
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.
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?
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.
The type-safe JSON feature in Manifold could be incredibly useful.