Java’s Checked Exceptions Are Evil? (2015)
blog.philipphauer.de
blog.philipphauer.de
And I think that's a very good thing.
The author, to me, misses the point. Don't rethrow the same exception. Handle it. It you want to handle it by throwing another internal unchecked exception, that's fine- it's certainly one way to handle it. But just modifying your signature so that you don't have to do the work of handling the situation is lazy and a great way to cause yourself bugs.
It leads to some top-of-the-stacktrace method having "catch(Exception e)" where there's little chance you actually know why this exception is happening, what lead to it, what it's about. Your users deserve better than that.
The alternative is that the developer doesn't know all the circumstances they must handle. They may not see a file not found error for years in production. Suddenly an unexpected exception of throwing up and we have to dig through user error reports and stack traces to figure out what happened.
You're better off returning something like Rust's Result type.
My point is that there's no reason to include special syntax/concepts for checked exceptions over just using values.
No, that's not fine. Typically, the layer immediately wrapping the throwing code has no knowledge about semantics. It's measurement code, logging code, caching code, transaction handling code, batch explode/aggregation code etc, but nothing that could make sense of a FileNotFoundException. The layer that wraps all this infrastructure code has more info, and can make sense of individual exceptions. By wrapping the original exception, this code now has to go on a getCause() safari to find out what happened. And yes, with checked exceptions there is no way around that, and that's the reason they should go.
If you take this approach, there are two kinds of methods: those that always succeed and those that can fail. It's similar to how Go's error type works.
Java is a mess not due to the existence of checked exceptions, but because they created an unfortunate exception hierarchy and that resulted in inconsistent API design where there's no consensus about what to do.
Never handle an exception (always rethrow) unless you know that you can do something specific in that exceptional case. In most cases just rethrowing the exception is the correct thing, and many bugs come from people mishandling exceptions.
A perfect example is InterruptedException, where really bad things happen if you don’t rethrow that.
Just rethrow, it’s generally the best thing.
Which one is "default" depends on if you're writing library code, application code, or service code. Especially in services rethrow, catch all at the top, and log is common and makes sense.
Probably less appropriate for a zlib or a file not found call. You'll have to do something (unless the answer is crash ... Which it is a sometimes!)
Either way I think the one thing I hate hate hate is, losing stacktraces! Don't throw a new exception without wrapping the old one. I need that info!
This! Throw as many RuntimeExceptions as you like, so long as you include the originating exception.
People often make the very big mistake of generically catching Throwable and then rewrapping that, which would include all Error types. Those should must be rethrown without wrapping. I don’t think you meant to imply that, but it’s easy to overapply advice like this.
1. How exceptional are exceptions?
2. Are committed to providing good language support?
If exceptions are the result of programmer error or terrible malfunction, then unchecked exceptions are fine. But in Java, exceptions are fairly common, even for common low-level stuff: read a non-existent file, parse a non-numeric string, look up a non-existent domain name. If you're going with that approach, it makes sense to type and control these.
As for language support...Java is interesting. It doesn't have unions in any of its types except for exceptions. But that makes it interesting trying to write generics, e.g. in a very functional approach. In my experience, most functional Java paradigms just don't support any user of checked exceptions and you make all exceptions unchecked.
Except I knew that it would always be receiving valid JSON because this code was downstream of a Kafka Streaming app whose entire purpose was to validate that the record was valid JSON that conformed to a JSON schema.
But the authors of Jackson require me to add a useless catch clause to my code. And seriously, an IOException for unparseable JSON? At least throw an UnparseableJsonException or something.
I will write catch clauses for exceptions that could reasonably be expected to occur and are recoverable from - but if an exception does occur, and you can't recover from it, what the hell is the point of a mandatory catch, log, and rethrow?
In my experience, 99% of the handling of a checked exception is to catch them, and rethrow in RuntimeException, which shows that the intent doesn't match the reality of how people are programming.
But, that said, I'm writing code on the JVM for our own internal apps - we're never going to have a desktop user facing a stack-trace. I think that when Java was developed it was intended to be far more user-facing than it is now.
Exceptions that can be thrown by a method should be part of the contract of that method. Changing the list of the list of exceptions that can be thrown by a method is just like changing the parameters of the method. This is something that should cause calling code to fail to compile. This will allow you to inspect the calling code and decode how to deal with the new exception.
When the exception that can be thrown by a method is not known you don't know what exceptions to catch and what exceptions to pass through to callers. You can't rely on documentation because the documentation is not verified be the compiler and so is not reliable. All you can do is to run the program a few times, see what exceptions you get and handle them. Even if you are able to determine the full list of exceptions using this strategy, a future revision of the called method can throw a new exception and cause your program to crash. As a result, in large C# code bases it is common to see catching the base Exception class because this is the only way to prevent a crash. This causes exceptions to be "swallowed" because higher level code that is actually prepared to handle certain exceptions never get the exception.
With Java this problem doesn't exist. When you call a function you know exactly what exceptions can be thrown and you can catch (or pass through) exactly those exceptions. There is no need to catch the base exception class.
Checked exceptions require reannotating the call graph on a change, but that's not surprising: you would have yo do the same thing for changes in result types. Failure modes are no less part of the interface. For generic code you need to be able to abstract over exception types, it seems that Java lacks the capability.
An escape hatch to dynamic unannotated exceptions would still be useful: C++ will mantain that.
Of course there are exceptions which are really return values that do need to be checked by the calling function, but that's a misuse of exceptions in languages which don't support lightweight qualified unions (such as those provided in ML/Haskell).
In any case I think Erlang's quite different approach to handling errors is far superior.
It's been a while since I've done any Java, but I seem to remember that you can just annotate your function with the "throws" keyword to propagate exceptions up the call stack, so that you can handle them somewhere else.
Because this is common and sensible, the code ends up with either many exception handlers that convert checked to unchecked or add the exception to the calling method signature which propagates the problem up that call stack
Programmer A writes this C# code:
class A {
public static void startFoo() {
if (/* Foo is not installed */)
throw new FooNotInstalled();
// ...
}
}
Programmer B calls the above code like this: class B {
try {
A.startFoo();
}
catch (FooNotInstalled) {
// Tell user to purchase Foo
}
}
Later programmer A updates his code because there are newer versions of Foo and he needs the newest version: class A {
public static void startFoo() {
if (/* Foo is not installed */)
throw new FooNotInstalled();
if (/* Foo version is too old */)
throw new FooVersionTooOld();
// ...
}
}
Now the code written by Programmer B crashes even though it compiles fine. That's bad.Had this been Java, programmer would be would be alerted to the fact that he needs to decide what do do if the version of Foo is too old. This is good.
So listing exceptions that can be thrown is a good thing because it helps you write more reliable code. If you are lazy you can always defeat the system by declaring your method as throwing a parent (or the root) exception class, in which case it is no worse than C#.
You're very likely to get some of the latter sneaking in with an interface change like the above. The person who made the change is going to be the one who gets stuck getting the code to compile. If they are faced with tens (maybe even hundreds) of compile errors, they're going to be forced to make a whole bunch of unplanned changes to a bunch of code they didn't write and might poorly understand even before they can get to the point of being able to start running the unit tests.
This is entirely valid Java:
public class FooBob {
class ExampleException extends RuntimeException {
}
public boolean foo(Boolean bob) throws ExampleException {
...
}
}
You don't need to throw a checked exception to document that your public API throws an exception.You're right, that throwing a new checked exception would force programmer B to handle it, but frankly, I'd rather that programmer B a) versioned his dependencies or b) read the source code of his dependencies.
Also, not sure about C# land, but in Java land, if you're writing libraries, we have tools to enforce semantic versioning based on public API changes (the Maven enforcer plugin with the semver plugin) which would also communicate breaking API changes.
I would much prefer that enforcing the communication of backwards-incompatible API changes not be done by the compiler.
if(!isInstalled(Foo)) {
purchase(Foo)
} else {
start(Foo)
}And of course writing "throws Exception" on every single test is hugely annoying. Yes, I do not want to handle exceptions in the tests, that's the whole point of the test!
My Java is very rusty at this point. But it looks like both SQLException and IOException are derived from Exception, so you could just specify the interface as:
UserRepository.getUsers(Path) throws Exception
No?An alternative would be to create a RepositoryException class, and warp the IO-/SQL-Exeptions into an instance of RepositoryException.
In any case I don't see a flaw with specifying an Exception type in the interface. It tells the developer concisely: "This can go wrong, and you should plan for this". And it's clearly better than C-style return codes which are hard to interpret and easy to ignore.
In the specific case of IO, I don't think whether a method involves IO or not is an implementation detail that should be hidden. IO is very costly and has very different failure scenarios than memory accesses/computation. You should always know whether you are calling a function or a remote service.
For functional programming languages, I think it's a lot more convenient to not have checked exceptions because how they affect the composability of closures. Not saying it's impossible, but imagine having to add throws clauses to your .map and .filter chains.
I see that this is inconvenient. This might be the essence of the whole thing here. It is way worse than adding a new argument to a function!
1. There is no way to overload a function with different Exception clauses.
2. You not only need to touch the call sites but also parts of the stack that led to the call sites.
> I've had to write code to open a file that I know will always exist
How can you ever be sure a file exists? I can be sure a function or a variable exists if I declared it. Files can be manipulated by other processes, USB drives are ejected, etc. You can never be sure they stay where it was. Even if I created the file one line above, I technically still have a potential race condition.
> For functional programming languages, I think it's a lot more convenient to not have checked exceptions.
There is a clear tradeoff here. E.g. when writing python or bash scripts I seldom care to catch errors. Let it crash fast and return a stack trace. For production grade software you usually want to much more careful and handle exceptions explicitly. Checked exceptions are clearly targeted for the latter.
Well I can't ever be sure, but the example I'm thinking of is looking up a config file on the file system that was copied there by a deployment script. If it didn't exist, a RuntimeException failing the application would be entirely appropriate.
If I was checking for a file on a USB drive or some other source, I'd very much have a try-catch. Do I need checked exceptions in this case? Still debatable in my opinion.
WRT checked exceptions targeted for production grade software, curious what you think about the (lack of) checked exception handling in Java 8 lambdas.
How do you know that the file will always exist? I'm pretty sure you don't. The try catch shouldn't be empty. Now it's going to crash when you try reading or writing from the file so all you did was make it so it blows up with a less helpful error later.
For example: you store a conversion factor in you config file (eurostodollars=1.12 or whatever).
If you have an empty catch block, you now have a null Reader object or something. Next read will throw an IOException, which you also swallow and you now have an uninitialized config value. Except that one will be zero instead of null.
500 km further in your code, a month after you started the app, that zero gets used in your code.
amounttopay+=eurostodollars*euroamount;
This will not fail immediately either. No, it will do something weird. In the best case, you crash for a really weird reason. More probably, you send out a file or web call or ... with insanely wrong data. Bonus points if you sign it with a private key, then you're legally bound by whatever nonsense you happened to promise. The next program in the chain might sit either in your company or not. It will swallow your garbage date wholly, do no checking of its own as this no-error-checking culture is pervasive. It will either crash and or do further damage. When the shit finally hits the fan, it will be a containerload of shit instead of a mouse dropping.I saw this kind of scenario again and again. No , the app cant possibly start without database, that never happens, and anyway the next user will notice the empty database. Except after power failure in the middle of the night, when the database is still booting up while your app is reading the config from it. When business hour start, everything seems fine. Until it isnt.
Checked exceptions are good if you want to be sure your caller does something when things go south. Some examples from my experience:
* Most things that should be told to the end user, i.e. Business Exceptions. E.g. a webshop user tries to buy something but your stock has been emptied why he was deciding if he should buy or not.
* Follow-up after a modification: If you wrote half a file, you can delete it in your catch IOException instead of letting it linger around. My company has a tendency to pick up and process any file found in an output directory as soon as you release the file lock. But just not filling your disk is good enough for me as reason.
Don't get me wrong, I also think that Java's checked exceptions are not really useful but your argument is very one-sided.
I end up converting checked exceptions to runtime exceptions all the time:
try {
...
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}I think Java executed badly here.
There doesn't seem to have been a clear policy in the standard library for what kind of exception should be checked, and there really ought to be a concise way to say "panic and abort the process" when that's appropriate.
Also, when you have checked exceptions (or similar things like error returns which it's compulsory to check), sometimes you need to add variants to library functions for cases which can't fail. For example, you might have a library function which takes a character-encoding parameter and can't fail if that encoding is UTF-8; you might be better off adding foo_utf8(x) which is like foo(x, UTF_8) except for the error specification.
There is, it is the Error class and its subclasses, e.g. OutOfMemoryError
To catch and rethrow an exception you still need a try and a block, and you probably add a level of indent.
Compare Rust, where you can generally spell "I'm sufficiently confident that this won't happen that I'm happy to abort if it does" as « .unwrap() ». (That's not a great word for this idea, but it is at least short.)
Furthermore, whether an error can be handled or not is a property of the caller, not the callee. If you're writing a public api and don't know your caller then checked exceptions are safer, and callers who just want to propagate the error can still write an api wrapper that rethrows them unchecked.
I do agree that checked exceptions are poorly implemented in Java though. In my dream world checked exceptions would be replaced by Either types which wrap a value or an error and there would be macro-methods for both propagating them and throwing them unchecked. There would also be union types for representing a set of possible named exceptions. Rust is probably the language closest to this.
Finally, if you're writing internal code for something like a web api, then of course you should stay far away from checked exceptions and rethrow them unchecked as early as possible when calling external code.
I think checked exceptions are clunky, irritating, but all-in-all fairly good solution. I'd rather be forced to check an exception than to bit know about it at the very least.
I think Java's situation could be improved with 2 very minor changes if we had a time machine.
1) Make throwing any exception an unchecked "RuntimeException". The point is just to remove the type-based duality. Functions that declare "throws" still need to be checked.
2) A cleaner syntax to rethrow "try { } rethrow;" maybe. This won't force your function to declare anything. That's always a personal choice.
These could address the core issues, but still require a little education around best practices. If I had to distill them I'd put it this way:
There's no shame in just rethrowing unchecked through the layers of your own code unchecked or crashing a thread or request if you planned it, BUT if an exception is leaving "your code" and called by "someone else" make it checked. Preferably use an appropriate type.
Example: I'm writing an encoding library on top of an HDFS client. My functions should declare FileNotFound or IOException even though the came from the client "below" me because I want to be a good citizen to the code "above" me. Internally, my code doesn't need 10 thousand checked functions though.
There are a few specific cases where I might recover, and it's absolutely okay to check the documentation for them.
I assume you agree that they should at least declare that they throw them? I agree that the rethrow should be painless at least.
It's unnecessary to communicate that some call might fail. It's even misleading: a novice might handle a checked exception and rollback some state, but not think of all the implicit exceptions that might occur, e.g. a stack overflow. I'm not even talking about subsystems being replaced by distributed versions (so now there are networking errors too) or similar issues arising from composability.
So resource allocation and state logic should be written in a way that assumes everything can crash and burn at any time. And then one can sprinkle recovery and retry logic on top of it, but only in very specific cases, and on specific layers of the application.
Other than that, they're randomly scattered around the oldest parts of various apis and mostly ignored.
Some kind of call-site use of the throws keyword perhaps?
byte[] bytes = "Example".getBytes("UTF-8") throws RuntimeException;
I can't really think of a reasonable use case for anything other than RuntimeException, but in the old days, some people would surely have loved to rethrow wrapped as MyCompanyException.
byte[] bytes = unchecked(() -> "Example".getBytes("UTF-8"));However, back in the Java 7 days, I think checked exceptions would've been made a lot better with a bit of syntactic sugar:
public void foo() rethrows* {
bar();
}
Where the meaning of the rethrows term is that the method throws whatever exceptions the methods it calls throws. The bottom level methods that throw exceptions would still haveThis would make it easier to refactor code to change exception behavior. It would only be at the public API boundaries that you have to wrap exceptions to avoid changing the API or modify your method signatures.
Now that lambdas are here, checked exceptions are even more painful, so I don't think this solution would tip the balance. But for the language that we had, I think it would've helped a lot.
* rethrows might not be the best name. I'm more attached to the idea than the term.
Since Java, did Ruby (several years ago). It was a revelation that using essentially runtime exceptions was not as big a cluster as Java community at the time thought it would be. Convinced me that if you're going to have exceptions, they should be runtime exceptions.
Since then, been doing golang and really liking it. The verbose error handling is sometimes tedious, but is the least evil way of handling things that I've worked with. Our experience has been that the quality of code improves when error handling is tied in with the code flow instead of dealt with as a separate path.
In particular:
> But how do I know, that a method throws an unchecked exception? The compiler doesn’t tell me. Yes, you are responsible to document unchecked exceptions carefully.
Except if you try that, the same problem that makes checked exception problematic will bite you here too - except now it's easier to blame yourself than blaming the compiler.
If you write
void foo() throws FooException
all you can really say is "It might be useful to consider FooExceptions when calling this function".- You can't guarantee to a caller that FooException is always important to consider - because, as the author correctly stated, there might be situations where you call foo() but it's formally impossible that a FooException is raised.
- You can't guarantee that FooException is the only important exception to consider either, because foo() might call other functions that might raise different exceptions on their own - a fact that you could probably read up in those other functions' documentations, if you knew you had to deal with those other functions in the first place.
This limitation stays whether FooException is checked or not.
In then end, what you'd like to know when calling a function is the complete set of exceptions this call site could produce at runtime. Checked exceptions were a an attempt to answer this question, even if it was a very clumsy and eventually unworkable one.
Maybe a better solution would be more sophisticated static analysis, which could try to answer this question as part of an IDE. However, this would require that the analysis tool get complete information about all abstract functions calls, interceptors and other things that could happen as part of a call. This seems somewhat doable with fully static languages like Go but seems to run completely against Java's philosophy of being extensible at runtime.
We live in a wonderfully polyglot world these days. At the time Java was introduced, I felt like new programmers encouraged each other to pick sides. Were you going to use C? Pascal? C++? (Visual) Basic? Ada? Lisp? Your "choice" brought you praise from one camp and ridicule from another.
Nowadays I spend most of my time on other languages and while it is more ergonomic, I do spend more time reading documentation regarding possible errors.
Just that instead of being a compiler error like in Java, the unexpected() handler is called; which by default calls terminate() to kill the application.
This behaviour has been removed in C++17.
However C++23 might introduce a similar syntax for value type exceptions, but following up semantics similar to how Swift does it.
A more ergonomic way to do that would probably make people stop complaining so much.
This problem is especially noticeable when using lambdas. If you're implementing an interface that doesn't have a throws clause, but the body calls something that throws a checked exception, you _have_ to introduce a try/catch to your lambda body. You don't get to re-throw it without wrapping it in a RuntimeException.