
Java’s Checked Exceptions Are Evil? (2015) - tosh
https://blog.philipphauer.de/checked-exceptions-are-evil/
======
mabbo
A checked exception is a way for a library to indicate "this is something that
can happen and it's up to you to deal with it". It's compiler enforcement so
that your user doesn't see "Exception thrown! <Stacktrace>".

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.

~~~
groestl
> Don't rethrow the same exception. Handle it. It you want to handle it by
> throwing another internal unchecked exception, that's fine-

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.

~~~
mabbo
I don't mean to wrap it in a generic exception, I mean to wrap it in a
context-specific exception written for the purpose. A
"UserDoesntExistException", or a "DatabaseNotRespondingException". Something
for the purpose that likely exists in a useful hierarchy.

~~~
groestl
This is better, but it could have been the original exception all along, given
the rare number of cases in real-world code where the call site actually knows
how to recover.

------
petilon
Having programmed extensively in Java, C# and C++ I can say with confidence
that checked exceptions is the most important thing missing in C++ and C#.
Around the time C# was released there was a lot of debate around the topic of
checked vs unchecked exceptions and this created an impression that Java's use
of checked exceptions was "controversial". In fact it is a feature every
modern language should have.

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.

------
gpderetta
As a counter point people have been trying to get the equivalent of checked
exception in C++ for a while and there are well supported proposals on the
table right now.

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.

~~~
rwmj
The trouble is that in most programs I want to catch most exceptions very near
the top ("main" function), not in the calling function immediately above where
the exception is thrown. In other words the check is useless and done in the
wrong place.

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.

~~~
simongray
> The trouble is that in most programs I want to catch most exceptions very
> near the top ("main" function), not in the calling function immediately
> above where the exception is thrown. In other words the check is useless and
> done in the wrong place

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.

~~~
smsm42
Yes you can, but the problem is that this means reannotating all the stack up
to "main" every time some tiny implementation detail changes - or you use
different JSON parser that has "JsonParsingException" and not
"JsonParserException", and so on. Or, you have to wrap them all into
generic/runtime exceptions, which negates the whole benefit.

------
apexkid
I think checked exceptions are great. When using a library you instantly know
what all bad could happen while calling a function and retry/handle on
specific exceptions. For example, when reading a file if you get something
like a FileNotFoundException, you can treat it as a fatal case or can simply
move to process the next file. This provides a lot more clarity to code and
resonates with the static type safety paradigm of java.

~~~
sorokod
I think that a common criticisms is that in many cases, regardless of whether
the exception is checked or unchecked, there is little the process will do but
give up.

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

------
petilon
Here's an example C# pseudocode to illustrate the problem with not having
checked exceptions:

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

~~~
pacala
Exceptions are for error reporting. Pass them through all the way to the top,
and log them. Enforce module preconditions proactively.

    
    
        if(!isInstalled(Foo)) {
          purchase(Foo)
        } else {
          start(Foo)
        }

~~~
smsm42
This! Java is hugely overusing exceptions for reporting situations that are
completely normal (probably because it didn't have anything like Option type
or any way to return error results). Exception should be for "somebody stole
our HD and we can't read data" or "database have died and we can't continue,
all panic!", not for "user made a typo and we need to display an error
message". Using exceptions for routine flow control is bad design.

------
heinrichhartman
> Moreover, the client doesn’t care if an IOException or a SqlException is
> thrown. He just wants to know, if something went wrong. Consequently the
> thrown exception should be implementation agnostic.

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.

~~~
leothekim
I can't tell you how many times I've had to write code to open a file that I
know will always exist and have to write an empty try-catch. Or had to add an
unused throws clause because an interface specified it. Sometimes I've even
had to throw a different type of exception than declared by an interface
method and was left to either refactor tons of code to uselessly handle the
new checked exception or throw a runtime one and catch it at the smaller
number of callsites, or throw it as one of the declared checked exceptions
that would not accurately describe the type of error to be handled. Anyone use
Hibernate and see how many times an underlying SQLException got wrapped by a
HibernateException?

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.

~~~
choward
> I can't tell you how many times I've had to write code to open a file that I
> know will always exist and have to write an empty try-catch.

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.

~~~
leothekim
That's an entirely appropriate failure mode for my case. If my app can't find
an important file, then it should fail immediately, alerts then go off,
dashboards are bleeding, ops is getting paged, etc.

~~~
hyperman1
You did not get that failure mode. If you want to fail at file not found, wrap
it in a RuntimeException or IllegalStateException or something. You don't even
have to create a new class, just use one of these 2 built ito Java.

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.

~~~
leothekim
Nothing about checked exceptions inherently prevents the failure scenarios you
list, particularly the one about the power failure.

~~~
leothekim
Also, I gave a bad example for empty try catch. In this case, I would want a
runtime exception thrown. Apologies for misleading the conversation.

~~~
hyperman1
In that case, you avoided disaster and I'm starting to agree with you.

------
monster_group
Checked exceptions make a lot of sense if they are business logic related
exceptions for example - CustomerNotFoundException,
WithdrawUnauthorizedException. The client knows that server will throw these
exceptions and can take different actions accordingly (for example displaying
an appropriate message rather than a generic error message). If it is just one
general exception then you are clubbing several different situations in just
one bucket.

------
mjw1007
In general it's hard to distinguish between a good idea executed badly and a
bad idea executed badly.

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.

~~~
kevinconaway
> there really ought to be a concise way to say "panic and abort the process"
> when that's appropriate.

There is, it is the Error class and its subclasses, e.g. OutOfMemoryError

~~~
mjw1007
I mean a bit more concise than that.

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

------
exabrial
Also, we also discovered in recent years it's likely best to handle exceptions
at the near the top, not the bottom by incorporate a TLEH (top-level error
handler).

------
glun
This article is far too focused on the domain of web programming where you
almost always propagate the error to the main loop. However, in a large amount
of programs you actually are able to handle the error at the call site.

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.

------
kjeetgill
Whew, this is a hairy topic. The go camp has the same conversation around
returning error codes.

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.

~~~
groestl
I absolutely loathe whenever I come across an IOException. I _know_ that
something can go wrong, everything will go wrong at some point, so my code
needs to crash appropriately, everywhere. Nobody has to tell me about that, so
no need for IOException or a MyLibraryException checked exception subtree.

There are a few specific cases where I might recover, and it's absolutely okay
to check the documentation for them.

~~~
kjeetgill
What? Do you think it's the libraries job to decide that the program should
crash or yours?

I assume you agree that they should at least declare that they throw them? I
agree that the rethrow should be painless at least.

~~~
groestl
I'm not sure I follow the first paragraph, but what I'm trying to say is that
the JVM can, at any point, present an unchecked exception, or an error. In
that case, at the very least, all resources have to be released, and nothing
should possibly be left in an invalid state. All this while the stack is
unwinding.

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.

------
exabrial
Yes, unless they are used for business logic where certain outcomes must be
accounted for.

Other than that, they're randomly scattered around the oldest parts of various
apis and mostly ignored.

------
usrusr
That whole discussion would not exist if java had a convenience syntax for
rethrowing checked exceptions wrapped in a RuntimeException, in less than the
customary four extra lines.

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.

~~~
bunderbunder
It's fairly easy to get something like this with Java 8. I have my own set of
wrapper functions that let me do something like:

    
    
      byte[] bytes = unchecked(() -> "Example".getBytes("UTF-8"));

------
hyperpape
I think checked exceptions are a bad way of doing Result types (Result in
Rust, Either in Haskell), and that those are generally better than unchecked
exceptions. In my opinion, unchecked exceptions aren't that much different
than checked exceptions. The difference is that you have to get them right at
the level of documentation, not type signatures, and documentation usually
doesn't have a compiler that will help you.

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 have

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

------
tomohawk
Did Java for many years. Their exceptions were a good next step when compared
to C++, but there are many problems. For example, InterruptedException. It
really should be a runtime exception, except that it is checked. I've very
rarely seen code that handles this exception properly, and quite often seen it
buried, causing all kinds of mischief at runtime.

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.

------
xg15
I think the authors has some good points, but I don't think the proposed
solutions really solve the underlying problem.

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.

------
smsm42
The biggest WTF for me was that checked exceptions are not compatible with
lambdas. So any concise elegant code that Java 8+ allows you to write, with
all new streams API and all that goodness, does not work the second you need
to use any class that has checked exceptions mentioned. I couldn't believe it
when I first learned about it - but it's true:
[https://stackoverflow.com/q/27644361/214196](https://stackoverflow.com/q/27644361/214196)
I still can't believe anybody thought it's a good idea to do it this way.

------
grashalm01
My rule of thumb: use checked exceptions if and only if your exceptions are
not supposed to be rethrown and must be handled by the caller directly. These
cases are very rare, but I recently had one them.

~~~
wtetzner
I think that's a reasonable rule of thumb, and it tends to be the way I use
checked exceptions as well.

------
vbezhenar
Checked exceptions are not well thought concept and I'm surprised that they
were introduced in Java. Java seems to be pragmatic language adopting best
practices after they were fleshed out in other more experimental languages for
decades. I consider checked exceptions to be semiofficially deprecated after
they introduced java.io.UncheckedIOException in standard library. So it's like
discussion is over, just use unchecked exceptions everywhere. Any sane library
does that anyway.

~~~
pjmlp
They were following up on CLU, Modula-3 and C++ exceptions design, the
predecessors that everyone forgets when complaining about them.

~~~
projektfu
I don't know what Gosling's actual influences were, but when Java came out I
was struck by its similarity in many respects to Modula-3. One convention I
wish they had borrowed was modules instead of classes as the encapsulation
technique, although I think many potential converts to the language would have
found Modula-3's partial revelation of types to be confusing.

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.

------
flipp3r
..wat. Either rethrow it, deal with it, or rethrow it as unchecked. As a Java
developer, you can make the choice. I really don't have any issues with
libraries telling me something can go wrong. I'd rather know something can go
wrong before putting my code in production than while it runs.

~~~
pjmlp
Not only that, the idea of checked exceptions actually appeared on CLU,
Modula-3 and C++ before Java came into scene, but somehow only Java gets
mentioned.

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.

~~~
vbezhenar
There are no checked exceptions in C++. throws in C++ is very different from
Java, even if syntax is similar.

~~~
josefx
You could declare throw() exceptions in C++. If you did that the runtime would
intercept and typecheck any thrown exception against that list. Calling code
could rely on that list since any not listed exception would cause the
application to terminate. Modern C++ replaced that with noexcept - a function
either can throw or it can't and template code can branch on that information.

------
9dev
I think the solution he proposed is exactly the way to handle errors. A
RepositoryException conveys anything something on a higher level in the
application should know - there was some error retrieving data from the
repository. Full stop.

------
frou_dh
IMO invisible control flow only carries its weight in fully expression-based
languages.

