
Checked exceptions: Java’s biggest mistake (2014) - flying_sheep
http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/
======
alasdair_
I like checked exceptions. Yes, sometimes (especially in the oldest APIs when
they were still figuring this stuff out), they were overused but mostly I
think they encourage developers to really think about what happens in the
failure case.

I notice this especially with less experienced developers and remote calls - a
lot of JS code I’ve reviewed in the past assumes the remote call will always
work, yet Java code from the _same_ developer will almost always correctly
handle the situation, simply because the exception is explicitly required to
be handled.

~~~
hota_mazi
Right.

They don't just "encourage" developers to consider error paths, they "force"
them to do so.

The concept of checked exceptions is very sound, as is the more general
concept of compiler enforced error checking. Very, very few languages have
that (only Java and Kotlin in the mainstream league).

Languages with a solid implementation of algebraic data types offer a good
first step in that direction but they still require users to manually bubble
and compose monadic values, which introduces an unnecessary, and sometimes
intractable, level of obfuscation and boiler plate.

All other languages provide weaker approaches to this concept that are library
enforced, not language enforced, and therefore more prone to being overlooked
since they require discipline from the developer.

~~~
augusto-moura
You are not forced per se, and can have the same level of obsfucation. It's
pretty common to find code ignoring exceptions or wrapping them in
RuntimeExceptions, like:

    
    
      try {} catch (Exception e) {
        throw new RuntimeException(e);
      }
    

Also not doing nothing, or the famous catch and log

~~~
zmmmmm
At which point it's super easy to identify in code review and slap the
developer on the wrist.

I find these arguments that posit incompetent / ignorant developers as a
hurdle a bit strange. If they are going to incompetently handle errors wrong
when explicitly forced to handle them I can't even imagine how poor their code
will be without any assistance from the compiler, and it seems awful to think
that you will have no way to identify such poor handling on review - you're
going to have to look up every function they call and check if it can return
an error or not manually.

~~~
Groxx
i.e. Go. Go can make sure you are aware that an error exists (often. linters
do pretty good here too), but it does next to nothing to help you _handle it
correctly_.

From personal experience: yes, little to no compiler help on errors takes an
enormous amount of effort by both authors and reviewers (and future readers)
to ensure correct handling. The vast majority of the time it's just `if err !=
nil { return err }`, which is very frequently sub-optimal. But without knowing
the call in complete detail, you can't judge if that's true or not... and it
may have changed since you last saw it.

IDEs help that kind of "is this optimal/correct" question quite a lot, but
they can't verify it either. It's question-marks all the way down, unless you
fully know all the code you call, which is often infeasible.

------
hrgiger
Well... I will take the bullet and confess that I do like checked exceptions.
When they are not miss|over used they transfer the enough required knowledge
what is to be handled. You dont need to handle? Transfer to higher levels on
stack. That is a great fit in my opinion for applications designed with
especially fault tolerance futures and using many external components. Not
saying that design of CE is perfect, they might be too broad that doesnt tell
you exact handle case, so it will leave you in the dark or in a call chain of
a()->b()-c()-d() there might be cases that b and c wouldnt need to have
contract in their method signature maybe compiler would decide if exception is
orphaned or needs to be handled.

~~~
narag
I just read the article in diagonal. It doesn't seem to address the reason
that I hate the thing: sometimes I don't care about errors at all. Let's say
I'm writing a kleenex program to explore some feature. Not nice if you make me
feel all kind of boilerplate.

Or maybe I just want to defer the error management to a higher level. Again
busywork declaring exceptions.

~~~
narrator
If you don't care about exceptions just put,

try { //your code } catch (Exception e) { throw new RuntimeException(e); }

around it and you'll be fine.

The thing about Java is that the code lasts for a really long time because
when it fails, it does so usually with an exception that points to the
problem. When promises hang in NodeJS or memory corruption happens in C, it's
much more annoying to track down problems.

~~~
elFarto
There's also this magic function:

    
    
      private static <T extends Throwable> T sneakyThrow(Throwable ex) throws T {
         throw (T) ex;
      }
    

Due to generic type erasure, the entire throw clause gets erased so it looks
like this method doesn't throw anything.

------
gordaco
I don't really dislike the concept of checked exceptions, but the awful way
they prevent the usage of functional interfaces and modern Java in general is
pretty infuriating. Functional interfaces and code that uses them should have
a generic way of being transparent to exceptions, but I'm not sure it can be
done without breaking the language.

Nevertheless, I still believe that Java's biggest mistake is not checked
exceptions, but the stupid distinction between primitive types and Objects
(caused because all the cruft present in the latter would have make basic
operations prohibitive in terms of performance, at least for early Java
versions) and all the associated boxing. I have seen some extreme cases of
performance degradation because of that (fortunately a refactor to use arrays
solved the problem, but this is not always possible).

~~~
mrkeen
> Functional interfaces and code that uses them should have a generic way of
> being transparent to exceptions

This irks me too. It's been a while but I believe the workaround is just to
define your own (checked) FunctionE, SupplierE, ConsumerE functional
interfaces. But maybe that causes other problems I've forgotten.

> the stupid distinction between primitive types and Objects

I also dislike the distinction between primitives and Objects. But I don't
think your argument follows. Performance is why the distinction exists.
Objects have the cruft and primitives don't.

~~~
tpxl
>This irks me too. It's been a while but I believe the workaround is just to
define your own (checked) FunctionE, SupplierE, ConsumerE functional
interfaces. But maybe that causes other problems I've forgotten.

You can do that, but you need to do it for every exception type ): I'd love it
if Java had templated exceptions.

>> the stupid distinction between primitive types and Objects

One of the slowest things I found out about C# was floats being objects and `a
< b` (or something similar) being a stack about 7 levels deep.

~~~
ossopite
> You can do that, but you need to do it for every exception type ): I'd love
> it if Java had templated exceptions.

It does have them. You can write an interface with a type parameter `<E
extends Throwable>` and declare functions inside it with `throws E`

~~~
culturedsystems
You can do this, but it's not ideal. A function can list multiple exceptions
in its throws clause, which share no common type except Throwable, but this
combination of exception types can't be represented by a single type parameter
(except for the common base type). So in a lot of cases, you lose information
about the specific exceptions your function can throw, which limits the value
of using checked exceptions.

------
aazaa
> The biggest argument against “checked” exceptions is that most exceptions
> can’t be fixed. The simple fact is, we don’t own the code/ subsystem that
> broke. We can’t see the implementation, we’re not responsible for it, and
> can’t fix it.

Here's what Oracle has to say:

> Here's the bottom line guideline: If a client can reasonably be expected to
> recover from an exception, make it a checked exception. If a client cannot
> do anything to recover from the exception, make it an unchecked exception.

[https://docs.oracle.com/javase/tutorial/essential/exceptions...](https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html)

\- checked exception for recoverable errors

\- unchecked exception for non-recoverable errors

So the argument that most errors can't be recovered from is _not_ a reason to
abandon checked exceptions. It's a reason to reserve checked exceptions for
those cases in which recovery is likely.

The main argument in this article appears to be based on a misunderstanding.

~~~
tootie
The argument is that an error that can be recovered from isn't an exception.
It should handled with standard guards (bounds check, null check, format
check, etc). There's no checked exception that can't be better solved with an
if/then statement.

~~~
nordsieck
> The argument is that an error that can be recovered from isn't an exception.
> It should handled with standard guards (bounds check, null check, format
> check, etc). There's no checked exception that can't be better solved with
> an if/then statement.

That's a philosophical position that is largely driven by the language (and I
suppose the ecosystem around it). I happen to agree with that position, so I
prefer languages like rust and go over java.

But I also know that if I try to fight the customs of the language I'm working
in, it'll end in a lot of pain and unnecessary angst; so, if I find myself
using Java, I grit my teeth and used checked exceptions.

~~~
tootie
Checked exceptions are optional in Java. If you look at a even an long-
standing enterprise platform like Spring, there's almost no checked
exceptions. I always make it a policy on Java projects to not allow checked
exceptions and it's never a problem. Anything in the standard lib may have to
be dealt with, but I don't allow commits with any throws declaration.

------
specialist
How would eliminating checked exceptions mitigate terrible API design?

I've never understood the angst. All the arguments reduce down to mitigating
terrible abstractions.

The Correct Answer is better APIs. Mostly, that means don't pretend the
network fallacies don't exist. Embrace them. Which generally means work closer
to the metal.

I'll say it another way. The problem is EJB, ORMs, Spring, etc. The
obfuscation layers.

Someone smarter than me will have to rebut the functional programming points.
I'd just use a proper FP language. Multiparadigm programming is a strong
second on the list of stuff you shouldn't do. (Metaprogramming is first.)

~~~
masklinn
> How would eliminating checked exceptions mitigate terrible API design?

Any API which has used checked exceptions was made worse by those, because
Java's checked exceptions are bad, and their use runs actively against good
APIs. So not having them would have made the corresponding APIs less bad (not
necessarily good, mind) by definition.

~~~
specialist
Thank you for replying. It helps me to better articulate my thinking. You're
my Rubber Duck.

Why would network programming (I/O, persistence, etc) look any different
whether the language was C, Java, GoLang or other?

Most of my code is error checking and handling. (And now logging too, which
I'll ignore here.) Is this abnormal? (Being rhetorical.)

Plenty of noob code ignores errors. I sort of thought we all decided that was
suboptimal. Java's response was checked exceptions.

The only argument I've ever heard that made any sense is the silliness of
catching exceptions so far removed from the root cause that your code can't do
anything about it.

So don't do that.

Really, why would any one design a system that way? Because network
programming is messy? Because it'd be neat to compartmentalize the messiness?

I've been able to cleanly separate the value add business logic from real
world messiness exactly one time. I was in control of the full stack, end to
end. Imagine something like a useful BizTalk. I had been inspired by postfix.
My engine would pass work to plugins, which didn't have to do any I/O of their
own. My work anticipated serverless and AWS Lambda, if those programming
frameworks (paradigms) were better designed.

It now occurs to me that the checked exception abolitionists are advocating
_Happy Path Programming_.

The only other feasible Happy Path Programming strategy I know of is Erlang.
I've only done an Erlang tutorial, nothing in prod, so this is just a guess.

~~~
rovolo
> Most of my code is error checking and handling.

> Plenty of noob code ignores errors.

You can encode errors into the return type of the function to force people to
deal with errors. Here's a good example:

    
    
      byte[] read() throws IOException
        // vs
      Optional<byte[]> read()
    

Both cases force the user to consider failure cases.

Checked exceptions are essentially modifying the return type already, just in
a different syntax. It's inconvenient because the nonstandard syntax for the
type doesn't work well with all the other return types. It's still a bit
inconvenient to use the other option in Java though because there's no
language level support for real tagged unions (sum type) (union type).
[https://en.wikipedia.org/wiki/Tagged_union](https://en.wikipedia.org/wiki/Tagged_union)

The other class of not handling errors takes a bigger conceptual leap:

    
    
      void write(byte[]) throws IOException
        // vs
      Optional<Void> write(byte[])
    

The caller could just ignore the value in Java, so your criticism is still
valid. However, you can force the caller to _never_ implicitly ignore return
values. If the function 'throws' by returning a value, you have to explicitly
ignore it:

    
    
      val _ = write(bytes);
    

If the function can't throw (i.e. the return type is strictly 'void'), then
you don't have to do anything:

    
    
      write(bytes);

~~~
jayd16
Optional<T> isn't a replacement for typed exceptions as you can't pass or
inspect the error details.

~~~
rovolo
That's correct. I used Optional because it's included in Java and it's
simpler. With Optional I could explain handling the presence of errors, but
not the inspection of errors.

A full replacement is the Result type (a specialized 'Either'):
[https://doc.rust-lang.org/std/result/](https://doc.rust-lang.org/std/result/)

~~~
jayd16
Yeah. Although, that has an explicit error type which is good and bad. The
errors are consistent and known but you're restricted to throwing them
together into a single type. I kind of like C#'s Task type where you can still
use the catch block pattern matching if you want to. Maybe dropping the catch
syntax is better though.

------
Imnimo
Some checked exceptions make a lot of sense to try to catch and fix.
FileNotFoundException is something my code can probably recover from - you
asked for a file, it's not there, let me ask for a different file. Having a
file reading method declare that it throws FileNotFoundException can be a
helpful reminder to make sure you handle that possibility.

But then there are other types of checked exceptions that are almost certainly
unrecoverable because they happen way down in some other third party code. And
then you get the endless chain of "throws" all the way back up the code base.

~~~
ragnese
> But then there are other types of checked exceptions that are almost
> certainly unrecoverable because they happen way down in some other third
> party code. And then you get the endless chain of "throws" all the way back
> up the code base.

No you don't. When your code calls ThirdPartyAPI and it throws one of these
checked exceptions that you know you can't recover from, you wrap it in a
RuntimeException and rethrow. Then it's totally invisible to the rest of your
code.

Likewise, you should never have a method that throws 10 kidns of checked
exceptions. You should be writing your own custom exceptions that wrap
downstream exceptions into forms that are useful for you and your code (or
people who will use your code).

The biggest issue with checked exceptions is that people refuse to think
through their unhappy paths.

~~~
gmueckl
Wrapping exceptions when re-throwing can be really useful. I think that this
feature is often underused, especially by people who complain about checked
exceptions.

~~~
singron
E.g. Future wraps any Throwable in ExecutionException, which is a checked
exception. But ExecutionExeption could wrap any exception! It may as well just
throw Exception.

I really would have loved e.g. Future<Value,IOException|FooException>.
Obviously it gets erased in the executor, but if your code holds on to it, it
could maintain checked exceptions over the async boundary.

------
chriswarbo
I've been using Scala quite heavily recently, having mostly used Haskell for
years, with distant memories of Java. Scala allows Java methods to be called,
but doesn't bother with checked exceptions, which has bitten me quite a few
times.

My preferred style of error-handling is Option/Either, since I can implement
the 'happy path' in small, pure pieces; plug them together with 'flatMap',
etc.; then do error handling at the top with a 'fold' or 'match'.

Exceptions break this approach; but it's easy to wrap problematic calls in
'Try' (where 'Try[T]' is equivalent to 'Either[Throwable, T]').

The problem is that Scala doesn't tell me when this is needed; it has to be
gleaned from the documentation, reading the library source (if available),
etc.

I get that a RuntimeException could happen at any point; but to me the benefit
of checked exceptions isn't to say "here's what you need to recover from",
it's to say "these are very real possibilities you need to be aware of". In
other words checked exceptions have the spirit of 'Either[Err, T]', but lack
the polymorphism needed to make useful, generic plumbing. The article actually
points this out, complaining that checked exceptions have to be
handled/declared through 'all intervening code'; the same can actually be said
of 'Option', or 'Either', or 'Try', etc., but the difference is that their
'intervening code' is usually calculated by the higher-order functions
provided by Functor, Applicative, Monad, Traverse, etc.

It's similar to many developer's first experience of Option/Maybe: manually
unwrapping them, processing the contents, wrapping up the result, then doing
the same for the next step, and so on. It takes a while to grok that we can
just map/flatMap each of our steps on to the last (or use 'for/yield', do-
notation, etc. if available). It would be nice to have a similar degree of
polymorphism for checked exceptions. Until then, I'd still rather have them
checked (so I can convert them to a 'Try'), rather than getting no assistance
from the compiler at all!

~~~
barrkel
The right way isn't Either / Option (though it's a lot more viable with top-
level type inference); it's unchecked exceptions.

Checked exceptions have bimodal usage, from the programmer POV. Either you
care about the exception, and you deal with it very close to the throw point,
or you don't care about the exception, and it should be handled far far away,
across many stack frames.

The former isn't a problem. It's the right thing if e.g. you try to open a
file and the file is missing, and you have reasonable error handling logic to
retry, open a different file, replace the file and try again, whatever.

The latter is where the issue lies. If you're handling errors far away, then
you're handling lots of different errors there, and you're not distinguishing
between them, because there's too many different failure modes. You're most
likely just in a loop logging errors, or terminating. You're too far from the
cause of the exception to do anything specific with it, the context is lost.
So the effort to transport the exception type throughout the call graph is
pointless.

tl;dr: checked exceptions are fine near the leaf of the call graph, but are
increasingly pointless towards the trunk.

~~~
dpc_pw
`Result<T, dyn Error>` from Rust (since I'm not a Haskeler) is a result that
can contain any error. Which is exactly what unchecked exceptions are.

Rust developers can fluently switch and convert between conrete `Result<T,
SomeErrorEnumeration>` and `Result<T, dyn Error>`. It works beautifully.
Libraries usually enumerate their errors (leafs), applications usually just
throw everything to one universal error bag.

~~~
chriswarbo
> Rust developers can fluently switch and convert between conrete `Result<T,
> SomeErrorEnumeration>` and `Result<T, dyn Error>`

That's something different, since both cases tell you that errors might occur.

In Java we can do the following:

    
    
        public int concreteChecked() throws FileNotFound {
          if (bar) throw new FileNotFound();
          return 42;
        }
    
        public int polymorphicChecked() throws Exception {
          if (bar) throw new FileNotFound();
          return 42;
        }
    
        public Either<FileNotFound, int> concreteEither() {
          return bar? new Left(new FileNotFound()) : new Right(42);
        }
    
        public Either<Exception, int> polymorphicEither() {
          return bar? new Left(new FileNotFound()) : new Right(42);
        }
    
        public int unchecked() {
          if (bar) throw new RuntimeException(new FileNotFound());
          return 42;
        }
    

I think your 'Result' examples are like the third and fourth examples above:
using a sum type, differing by whether the error is more/less specific.

The first and second use checked exceptions, again differing in whether the
error is more/less specific. Importantly: these will refuse to compile if we
don't have 'throws ...' in their signature.

The last example uses an unchecked exception: if we throw 'RuntimeException'
(or a subclass), we don't need to put 'throws ...' in the signature, and hence
the compiler won't keep tell us to put 'catch' block anywhere.

~~~
dpc_pw
In Rust you can do `Result<T, dyn SomeErrorInterface>` as well, which makes it
able to express everything exactly like Java can. It's just Rust community
generally doesn't bother with the taxonomies of errors. You either get a
concrete list, or "any error".

------
captainmuon
I think checked exceptions are backwards. If you use a `throws` declaration
the caller _must_ catch it. It quickly becomes quite onerous. (Especially if
you come from the philosophy that an exception often means "abort this program
- unless somebody catches this". In small programs you might just want it to
crash early.) And even worse, it is not exhaustive. You can always get a
RuntimeException or a NullPointerException from nowhere.

It would be great if they worked the other way around. Instead of _forcing the
caller_ to catch an exception, they would _guarantee_ that no exception leaves
a certain block.

So you would have a function

    
    
        void MyFunc() onlythrows IOException {
            first();
            second();
        }
    

And the compiler would statically guarantee that no other exception can leak
out of it - because first and second have been marked `onlythrows IOException`
or are "pure" and cannot throw at all.

For sure you'd need an escape hatch, like Rust's "unsafe". And it would not be
very useful around legacy libraries. But it would be tremendously useful if
you could drop a block like

    
    
       neverthrows NullPointerError { ... }
    

in your code and be sure that everything inside is null safe! I asked about
this a few years ago on StackExchange [1] but so far I never heard about it
anywhere else.

[1]
[https://softwareengineering.stackexchange.com/questions/3497...](https://softwareengineering.stackexchange.com/questions/349775/different-
kind-of-checked-exceptions-guarantee-to-only-throw-x)

~~~
lultimouomo
> I think checked exceptions are backwards. If you use a `throws` declaration
> the caller must catch it.

I'm not sure I I'm reading your comment right, but this is plainly false.
Caller can add a throws declaration themselves and catch anything.

It seems to me that what you're advocating bears no difference at all with
checked exceptions. The "unsafe" escape hatch is called RuntimeException.
"throws" behaves exactly like your advocated "onlythrows".

The only actual difference you're proposing, AFAICS, is that you'd like java
to handle nulls differently - and I think we can all agree on that.

~~~
captainmuon
> Caller can add a throws declaration themselves and catch anything.

You are right. I meant someone in the call stack has to catch, not the
immediate caller.

That I mentioned null pointers is a red herring. I want to add a block that
tells the compiler "prove that no exceptions (checked or unchecked, not even
NPE)" can escape outside of this block!".

The escape hatch is about this: imagine you have a function that throws if the
argument is odd, but you know you will only call it with an even number:

    
    
        void myFunc(int i) never_throws {
            swear_never_throws(NumberOddException) {
                throwsIfOdd(i*2);
            }
        }
    

(Apologies for the pseudo-code, I haven't made up a nice syntax.) If you mark
your function that no exception can escape (not even an unchecked exception!),
but the compiler sees that throwsIfOdd can throw NumberOddException, you must
of course assert that what you are doing is OK.

------
iso8859-1
The confusion regarding checked exceptions (which are fine, if not misused,
see aazaa's answer) was made much worse by IDE's such as Eclipse, which would
generate:

    
    
          catch (MyCheckedException e) {
            e.printStackTrace();
          }
    

This causes unreliable programs, since the programmer will initially only
think about the successful path. Eventually, the exception will get thrown,
and things will break in weird ways. They may not notice it quickly, because
the stack trace will be buried in logs. Alternatively, if the IDE default has
been

    
    
        throw new RuntimeException(e)
    

or something similar, which would crash the program, the programmer would have
noticed it more easily. Of course, the program would still be broken, but
better crash hard and violently than subtly and confusingly.

~~~
sreque
Checked exceptions are far from fine, and this has nothing to do with IDE code
generation.

Monads are highly cumbersome, unwieldy, and difficult to use, but Haskell
programmers put up with them anyways because they get specific value from
them, being able to say things like:

* effect tracking * continuations * tracking which methods perform I/O * tracking errors

Java checked exceptions are like monads, but worse: they are even more
unwieldy and interact poorly with the rest of the language. And yet, unlike
monads in a language like Haskell, they provide practically zero value for
their cost.

There is a reason no language since Java has copied checked exceptions as a
feature, including C# which started as a direct rip-off of Java. There are
better ways to encode errors into a method signature to try to force the
caller to consider them than checked exceptions.

------
grey-area
According to Gosling, including classes/inheritance was his biggest regret.

 _I once attended a Java user group meeting where James Gosling (Java 's
inventor) was the featured speaker. During the memorable Q&A session, someone
asked him: "If you could do Java over again, what would you change?" "I'd
leave out classes," he replied._

[https://www.infoworld.com/article/2073649/why-extends-is-
evi...](https://www.infoworld.com/article/2073649/why-extends-is-evil.html)

------
pmcollins
Possibly unpopular opinion: Java's biggest mistake, by far, was annotations
that define behavior _at runtime_.

So now we have consultingware like Spring where if something isn't working, it
could because you missed an annotation somewhere, or put the right annotation
in the wrong place. Which annotation? Where? Maybe you'll find out a week from
now that you made a mistake, when a customer finds a bug in production.

This took all of the compile-time checking goodness that you got from Java and
threw it in the garbage. Now you either have to call an expensive consultancy,
read books/manuals about your gigantic framework (fun!), go on forums, etc.
You can't just use your coding skills.

I still often use Java for my side projects because I love it without runtime
annotations, but thank god for the rise of Golang. I'd rather deliver pizza
than go back to the misery that is annotation-driven development in Java.

~~~
xienze
The alternative is heaps of XML or JSON configuration, detached from the code
where it’s used. Consider that if you wanted to inject a bean in Spring XML it
requires creating a bean definition that in turn defines all the beans
injected into it, which in turn require their own bean definitions, etc. Then
you have to declare exactly which field/method the bean is injected into. If
you were developing software in the pre-annotations day you’d understand how
much that sucks. The annotation approach is much better in comparison.

~~~
bcrosby95
> inject

I see the problem.

The whole point of spring XML was so you didn't have to "write code" to wire
things up. Now we're using annotations to replace XML - we're writing code so
we don't have to write code. It makes no fucking sense.

Annotation injections are a completely ridiculous turn of events.

~~~
xienze
> Now we're using annotations to replace XML - we're writing code so we don't
> have to write code. It makes no fucking sense.

It does though. Turns out that writing XML configuration means you don’t get
to take advantage of the context associated with an annotation. I.e., if I put
@Inject on method something(X value) in class A, all the context of what type
to inject and where comes along for the ride. In XML I have to explicitly
specify every single bit of context, and oh yeah, if I rename “A”, “X”, or
“something” I better fix that in the XML or my program will blow up. Not a
good look for a programming language that already gets grief about being
overly verbose! Annotations just flat out make the configuration part of the
equation easier.

~~~
Supermancho
> I have to explicitly specify every single bit of context,

You still are doing that, just in a less clear way and a less debuggable way.
Most other languages get along without meta-languages (there are some
frameworks that are spring-like) because being explicit is better than being
implicit.

~~~
xienze
> You still are doing that, just in a less clear way and a less debuggable
> way.

That’s debatable to a degree. If I put @Inject on a field it’s pretty clear
what’s going on just from a quick glance of the source code. By contrast, I
don’t know injection of some field happened _unless_ I take a gander at the
XML config. And the debuggability of both approaches is the same, the
injection manager is doing the same magic under the covers, only the
configuration is different.

------
crehn
On a slight tangent, I'd rather have _only_ checked exceptions, so I know
exactly what might throw where. Instead, I have to rely on potentially
outdated Javadoc and debugging runtime exceptions in production to find them.

~~~
throw_away
I wish that handling/rethrowing wasn't required, but that at dev-time I could
just ask, hey, what's every kind of exception that could be thrown out of this
expression & then decide which ones I wanted to handle at this level, if any.

~~~
Nursie
You can ... ?

Handle some, declare the method throws the others?

~~~
throw_away
I don't want to have to write the declares part. I don't want my callers to
have to do so, either. I want the nice list of unhandled exceptions the java
compiler gives you, but I don't want to have to do anything about it if I'm
cool with those exceptions being tossed up a level.

Kinda like if everything was a RuntimeException, but I had a way to figure out
what are all the subclasses of RTE that this expression could produce.

Consider how Swift does exceptions. You mark methods as throwing methods, but
you don't say what kinds of exceptions can be thrown. If you call a throwing
method, you have to write a catch, but in order to figure out what kinds of
different things can be thrown, I have to rely on documentation or on
examining the source. AFAIK, there's no way to figure out all the different
types of exceptions that can be thrown.

I want something in the middle. I want the conciseness of swift, but the info
provided by java while I'm writing. I'm no language designer, so I don't even
know if that's possible, but it's the kind of pony I want.

~~~
Nursie
That Swift way sounds terrible for me.

I do want to know what the error-path contract is, and I do want it enforced
that I deal with them. I've seen too much crap in my time that just assumes
the happy path, when error handling and recovery are just as important, IMHO

------
hodgesrm
If checked exceptions overall are Java's biggest mistake, then the
InterruptedException implementation in particular is the second.

It conflates thread management with exception handling in a way that's
difficult to understand and implement correctly. The relationship between
InterruptedException and the Thread.isInterrupted() method is a particular
pain point for coders.

~~~
mac01021
What, in your opinion, would be a better mechanism for interrupting threads in
Java (aside from just making it an unchecked exception)?

Something like Erlang, where any process will just die upon being sent the
exit message, whether it's blocked on IO or receive or busy in the CPU, would
definitely be simpler and easier to reason about. But that capability carries
a runtime cost.

Go's mechanism is also arguably simpler, in which goroutines are not first
class objects and if you want to be able to interrupt one then you have to
write ad hoc logic using a channel that you provide specifically for the
purpose. But in 99.9% of cases, I think Java's more complicated mechanism with
first class threads is more convenient.

I would make it an unchecked exception, though. And I wish the old
java.io.Socket operations and similar methods would throw
InterruptedException.

~~~
hodgesrm
This is a really hard problem. If you use exceptions at any level they have to
be generated consistently, or the solution will be what we have now.

Because that's the other thing--you can't guarantee InterruptedException will
even be delivered to a thread. An underlying library can just eat it or the
thread could be waiting on a socket [1], etc. This kind of behavior the bane
of correctness or even getting operations like clean server shutdown to work
at all in some cases.

So I think this really needs to be something like CSP that's built into the
language in a way that makes the behavior consistent in all cases even if it
introduces coding patterns that have other costs. Java _did_ get object
locking and data visibility right by building in simple primitives like the
synchronized keyword into the language. You can create deadlocks but the
behavior is clean enough it's not hard to program around them.

I would also be fine with just dying as long as there is a way to clean up
shared data structures. However, that can't be manual because it's just about
impossible to ensure that such cleanups are correct. Databases use
transactions to get around that problem.

[1] [https://stackoverflow.com/questions/1024482/stop-
interrupt-t...](https://stackoverflow.com/questions/1024482/stop-interrupt-
threads-blocked-on-waiting-input-from-socket)

------
josephcsible
I don't think the whole concept of checked exceptions isn't a mistake,
although the way they're implemented certainly is. In my experience, the
problems with them almost always stem from one of two issues:

1\. Built-in exceptions that are checked but should be unchecked, IOException
being the main offender (I don't mean things like FileNotFoundException; I
mean the kind you can get if the OS returns -EIO)

2\. Lack of exception polymorphism, preventing you from doing things like
l.stream().filter(SomeClass::somePredicateThatMayThrow), even if the function
that you're doing it from can throw the same exception that the predicate can

I think checked exceptions would be great and nobody would hate them if those
two problems were fixed.

------
kasperni
This is still one of the pieces written on error handling.
[http://joeduffyblog.com/2016/02/07/the-error-
model/](http://joeduffyblog.com/2016/02/07/the-error-model/)

------
pjmlp
I sorely miss them in .NET, specially with libraries without any kind of
documentation regarding the errors that they throw.

Also checked exception haters always overlook the fact that CLU, Modula-3 and
C++ did it first.

~~~
zokier
If anything, I think _un_ checked exceptions were the bigger mistake in Java.

Checked exceptions are pretty much isomorphic to result-types that are oh so
fashionable these days (see also Rust), unchecked exceptions on the other hand
are completely invisible and unpredictable crazyness.

~~~
Guvante
`OutOfMemoryError` is the root of unchecked exceptions, you can't put it
everywhere because then it might as well be nowhere.

Rust improves on this by using a different syntax for unchecked exceptions
(`panic!` vs `Result`) which provides the benefit of discouraging unchecked
without preventing it.

~~~
tsimionescu
Aren't panics() in Rust unrecoverable in release builds?

Except for this minor point, I agree - you can't really make a managed memory
system without resorting to unchecked exceptions. Array index out of bounds is
another good example of an almost unavoidable exception that would only
pollute the code if it had to be checked everywhere (as is Null pointer
exception, but that of course could be mitigated by not having nulls in the
language).

~~~
ragnese
I don't believe so, no.

You _can_ set panics to automatically abort, though. But that's opt-in.

The real point is that panics are absolutely unchecked exceptions, but it's
really awkward to catch them and the standard library and entire Rust
ecosystem has a good, solid, culture around returning Results whenever
reasonable.

The problem with Java is that a bunch of people have no idea what they are
"supposed" to do with the unhappy path parts of their programs.

~~~
tsimionescu
Well, as long as the option of disabling panic handling exists, and if it is
somewhat widely used in real applications, library writers can't rely on
panics as an error-handling mechanism, so generally they have to treat it as
if panics can abort the program.

I think the problem of deciding what to do on the unhappy path is often very
difficult, a lot of the time much harder than the actual happy path. This is
true regardless of the error reporting/bubbling mechanism.

------
mumblemumble
I'm a relative newcomer to Java, and, being a newcomer, I have put some effort
into exploring as many corners of the language as I can. One that's proven to
be a particular puzzle is checked exceptions. But I think I finally understand
them now.

I quickly found that checked exceptions just do not play nice with any sort of
functional-style programming, like the article describes. But the problem goes
so much deeper than that. Checked exceptions are also, as far as I can tell,
incompatible with an object-oriented programming style. More or less for the
same reason that they interact poorly with FP. The fundamental problem is that
checked exceptions don't really play nice with polymorphism or higher-order
programming of any type.

Which takes us to the crux of how I understand them now: Checked exceptions
may not go well with FP and OOP, but they make all the sense in the world if
you're doing procedural programming. There, you're not trying to create deeply
nested abstractions, and you're not messing around (much) with polymorphism
tricks. The code's very lexically organized, with little in the way of
dependency injection or higher-order programming. When you're programming
procedurally, it's fine to be exposed to the implementation details of stuff
further down on the call graph, because you're the one who put it there in the
first place.

And that, in turn, means that checked exceptions are not really a mistake.
They're just a piece of evolutionary history. Because, early on, Java wasn't
_really_ an object-oriented language. It was a deeply procedural language with
some object-oriented features. It arguably still is, it's just that there's
been a big cultural shift toward trying to take a more object-oriented
approach since Java 5 came along and made it more practical to do so.

~~~
sreque
I agree with the part of your post where you show that checked exceptions are
bad, but IMO Java has been OO from the start. And, if by procedural code you
mean code that has zero abstractions, then, yes checked exceptions aren't a
problem there because their big problem is you can't abstract over them.
However, I find this statement more to be an obvious tautology than a
statement that checked exceptions are really useful in any situation.

~~~
mumblemumble
My bias there is that I'm one of those Alan Kay worshipping hard-liners who
thinks there's a lot more to object-oriented programming than simply using
objects. Similar to how there's more to functional programming than using
first-class procedures.

So yeah, it's true, Java has classes. But its culture and idioms and standard
libraries and even some language features (checked exceptions, for example)
are forever pushing developers toward procedural idioms. Less so now, perhaps,
but intensely so in the 1990s.

~~~
sreque
IIRC, Alan Kay says OO is message passing, encapsulation, and extreme late
binding. In java:

* message passing is virtual method invocation

* encapsulation is through public vs private members

* late binding is through the JVM dynamic linking system.

While Alan Kay may have had something much more flexible like Smalltalk in
mind, I believe the Java OO system meets the spirit of OO well enough and
makes certain tradeoffs for good reasons.

~~~
mumblemumble
Alan Kay was also fairly specific about what he meant by encapsulation, and
it's not just making things private. For example, he wrote, "Doing
encapsulation right is a commitment not just to abstraction of state, but to
eliminate state oriented metaphors from programming." And, later in the same
paper, "Human programmers aren't Turing machines—and the less their
programming systems require Turing machine techniques the better."

Java's programming culture tends to favor a shallower approach to OOP that
cleaves very closely to the fiddly, imperative, state manipulation-oriented
approach that is emblematic of procedural programming.

A great example of this is Java 8's streams API. There's absolutely no way to
evaluate a stream without introducing a state change that will alter its
behavior. Which means that, unlike for almost any other comparable API in
another language, you can't safely pass around and share instances of Stream
for fear that someone might break it on you. It's the shallowest possible
interpretation of the abstraction in question, and making it that way was a
deliberate decision that motivated by the tacit understanding that Java
programmers have a fundamentally procedural way of thinking about the world,
and would be confused by something that was truly declarative. The only other
language I'm familiar with that takes the same approach is another popular
"procedural programming with objects" language, Python.

This isn't to say that people _can 't_ do principled object-oriented
programming in Java. Just that few people do. In part because, if you try,
you'll end up constantly picking fights with the JDK. Not entirely unlike how
you _can_ do principled functional programming in Java, but it's an uphill
struggle.

~~~
sreque
That statement is neat but it looks to have little to do with programming and
more to do with programming style. What about Smalltalk, the language, for
instance, helps you "eliminate state oriented metaphors" any better than any
other programming language?

------
crehn
While we're talking about terrible decisions, can you guess what the following
code will print?

    
    
      String s = null;
      switch (s) {
          default:
              System.out.println("Hey");
      }
    

Hint: it will throw NullPointerException.

~~~
rwmj
I'm confused why you'd want it to do anything else. Perhaps you could give a
more realistic example to motivate the argument for why the behaviour is wrong
or inconvenient?

~~~
rovolo
It's inconvenient because null could be considered a case

    
    
      switch(s) {
        case null: return false;
        case "y": return true;
        default: return false;
      }
    

The broader issue is that Java handles null inconveniently.

1) Every object can be null, switch requires the argument to be non-null, and
the type system doesn't warn you when NPE are possible. A type system which
handles nullability could fail to compile if 's' is nullable. Kotlin does
this, and it let's you opt-in to the NPE with some convenient syntax:

    
    
      switch(s!!)
    

2) It's inconvenient to handle null as a value. To properly handle the null
case without throwing, you need to do one the following:

    
    
      if(s == null) { ... }
      else switch(s) { ... }
    
      switch(s == null ? "some-default" : s)
    

The first way can be made more convenient if you change switch to work on
nullable values. The second way is inconvenient, so people generally skip it.
If you want switch to only work on non-null values, there're more convenient
syntaxes to handle null, such as the 'elvis operator':

    
    
      switch(s ?: "some-default")

~~~
whitenoice
You could also use Optional, like

Optional.ofNullable(s).map(i -> {switch (i) {...}}).orElse()

------
jarym
Mistake maybe but I disagree on the ‘biggest’ part.

For me type erasure is a bigger issue. I get that it was done for backwards
compatibility but the drawbacks imposed by that decision seem to only grow as
more time passes and more new compromises have to be made.

~~~
mac01021
What, in your opinion, is the biggest drawback of type erasure?

~~~
nikeee
Some things that came to my mind:

\- List<T> doesn't "just work" with non-reference types. It needs boxing that
increases memory usage and introduces stuff like ints being null.

\- We need special functional interfaces for non-reference types for that
reason (e.g. IntConsumer).

\- This also affects Stream<T>, so we need IntStream etc.

\- A method must have a parameter of that generic type (or it has to belong to
a class that has this generic type). It otherwise becomes indistinguishable
during rumtime. For example, a method like ImmutableList.CreateBuilder<T>() is
not possible in Java (that example is from C#'s collection types).

Type erasure moslty comes into play when looking at non-reference types. For
reference types, it seems to work pretty good (although it's weird that
Map<String, String> will have the same runtime type as Map<Object, Object>).
The last point I mentioned is not good, but no deal breaker. If generics would
be like in .NET, we wouldn't have any of these restrictions.

With type erasure, we ironically have to write more java code while not being
able to express stuff in an abstract manner (Stream<T> is incompatible with
IntStream).

~~~
sedatk
Thankfully, C# team made the right call and used reification.

------
imglorp
I just want to throw a plug for the concept of Railway Oriented Programming.
It can be laid on top of almost any functional-ish language that can implement
some sort of Result(Ok,Err) return type. And to the OP's point, you can catch
exceptions where they occur and `return Result(Err(details))`.

We applied ROP with great success at a fintech where we wanted to clean up a
block of business logic with many failure paths. Instead of a forest of nested
conditionals or try/catch mess, there was a very simple happy path with clear
handling for all the errors.

Here's a good start. Ignore the language details, the concept is universal.
[https://fsharpforfunandprofit.com/rop/](https://fsharpforfunandprofit.com/rop/)

------
ben7799
I've been using java since Pre-1.0 and professionally since 1999 and I don't
see checked exceptions as being particularly high up the list of java issues.

Even with all the functional stuff they almost never seem to really create a
major issue.

My biggest issue with Java is just the way they've caved and constantly added
new stuff that is always grafted on so it's never quite as good as a language
that focuses on that programming paradigm from the start.

But none of the problems in the language compare to the scale & scope of the
problems caused by Java's default developer & architect culture. The culture
is terrible... everything gets overcomplicated, overabstracted, etc. and
you've got charismatic charlatans convincing wide swaths of developers to
misuse and abuse language features in ways that have made a lot of people hate
the language and have produced a lot of buggy and hyper inefficient code.

Java itself doesn't have to be bloated, slow, buggy, and a massive memory hog.
But the java developer community has continually made decisions to structure
their java software in a way that makes that the default condition of Java
systems. The way the Java language constantly gets new giant features grafted
on plays into this.. everyone jumps on the latest language addition and
misuses it for a few years before they come to understand it. By the time it's
understood there's something new to move onto and abuse.

Java became everything about C++ it was originally supposed to simplify.

~~~
barrkel
The reason for the complexification is the culture of unit testing, and in
particular cargo culting that idea to mean every class needs a corresponding
test class, every method needs at least one corresponding test method, etc.

In order to tests classes in isolation like this, all dependencies must be
injected, so they can be stubbed or mocked.

Because there's no universally available IoC framework or baked-in
functionality, everyone takes the library approach to DI, which means
factories of various levels of abstraction, and an excess of parameters.

But in practice most production Java systems do run with an IoC container,
which in turn makes more fun things possible - AOP, interceptors, automatic
transactions, all manner of magic.

(There's no doubt that checked exceptions were a mistake, to my mind, but the
mistake is being repeated in Rust, so I guess that lesson hasn't been learned.
But perhaps all checked exceptions needs is a better type system to manipulate
the error types through the control flow graph. We'll see.)

~~~
mabbo
> In order to tests classes in isolation like this, all dependencies must be
> injected, so they can be stubbed or mocked.

This really boils down to which school of testing you follow. London (Mockist)
vs Detroit/Chicago (Classicist). [0] is a good article on the different views
of testing. Made me think more about it.

I find that using fewer mocks, and unit tests that integrate many classes (vs
one set of tests for each class) makes Java programming more fun- but it is
very much a Detroit school way. And that's okay, but it's good to really think
about the differences.

[0][https://medium.com/@adrianbooth/test-driven-development-
wars...](https://medium.com/@adrianbooth/test-driven-development-wars-detroit-
vs-london-classicist-vs-mockist-9956c78ae95f)

~~~
flerchin
Good article/comment. Thanks!

------
adrianmonk
There are two kinds of exceptions: (1) those that can (and should) be handled
in a meaningful way, and (2) those there's no way to handle and you should
just crash.

A big part of the reason people hate checked exceptions is that actually doing
#1 correctly is really damn hard. It's a whole separate dimension of
complexity that your design needs to tackle.

A compiler that checks exceptions forces you to do it. Abruptly, if you're new
to the language. It flips the floodlights on at full brightness and makes you
see the full scope of the problem. It's tempting to shoot the messenger.

------
Reason077
The biggest mistakes in Java are checked exceptions, NullPointerException, and
primitive types. But which one of these is the _worst_ mistake really depends
on your perspective, and the mood of the day.

~~~
sedatk
\- primitive types \+ not having value types

FTFY

------
devit
Rust has a system equivalent to checked exceptions.

However Rust, unlike Java, has a great macro system and can thus easily
generate higher level exceptions wrapping the lower level ones.

~~~
masklinn
Rust's macro system is not what makes its "system equivalent to checked
exceptions" bearable.

That the language provides tools to operate on both results themselves and
their content (in part because results are reified and thus normal values of
the language, and in part because specific tooling like `?`) is what does
that.

Also that there is no issue of misclassification as in Java, because
everything is a result and that's that.

------
nayuki
In my eyes, Java's biggest mistake is that the byte type is signed instead of
unsigned. Masking a signed byte with (b & 0xFF) causes so much needless pain,
and I have never wanted to use a signed byte. On the other hand, I appreciate
that Java doesn't have unsigned versions of every integer type; that
simplifies things a lot. As for checked exceptions, I'm still undecided on
whether they're a good or bad thing.

------
samfisher83
I liked checked exceptions. It makes you aware of what exceptions that might
happen. Makes you think about how to handle it.

------
spion
The sad bit about checked exceptions is that everyone compares any sort of
error tracking to them and immediately dismisses many useful ideas

Java's checked exceptions got the worst possible combination of error
tracking. Its optional, so you don't even get to see if a function throws
(kind of like the billion dollar null mistake) and its based on classes, which
means a lot of irrelevant names leaking throughout the codebase.

Like with nulls, the main value is being able to claim that a function doesn't
ever throw, at all. Apple's Swift got this just right.

A simpler system of "throws / doesn't throw" and with optionally polymorphic
variants / unions to complement it would go much further.

------
bcrosby95
The biggest problem with checked exceptions is that the application writer
decides what is a recoverable error. Not the library designer. I could easily
imagine a program where SQLException is a recoverable error. But I don't write
those types of programs.

~~~
merb
well the problem is that SQLException is not a good exception at all. it
actually includes the following errors:

    
    
        - database connection closed
        - SQLTimeoutException (o.O)
        - protocol exceptions like duplicated key
        - query exceptions
    

the problem is that some of them are recoverable but usually it's more of an
hassle and duplicated key exceptions are easier to handle in app code via
upsert, etc.

so basically the problems are library designer errors and not application
writer errors.

checked excpetions should only be exceptions that a developer COULD handle and
not ones that he can't

in C# the library designers usually create a "Result" object that has a
boolean of succeded or status of enum instead of using exceptions for
failures. i.e. most i/o errors are not recoverable, thus c# does not enforce
you to recover from them.

~~~
cletus
Yeah it's reasonably common for people to try and salvage checked exceptions
by saying people are doing them wrong but at some point you realize the best
you're doing is trying to put lipstick on a pig.

Here's a bigger problem: checked exceptions pollute your API and expose
implementation details. Consider an API that stores and retrieves objects. A
particular implementation does so by writing them to a database via JDBC so
you get SQLExceptions. You have two basic approaches:

1\. Include SQLException in your function signatures so the caller can deal
with it. This exposes the implementation detail; or

2\. You can hide it by transforming that checked exception into something
specified for your API. At this point, what benefit have you gained from
SQLException being a checked exception? You're hiding that detail.

For (1), you're baking checked exceptions into your function signatures such
that it can be really difficult to change later on.

If you sit down and think about the practicalities the argued upsides of
checked exceptions are essentially nonexistent and unchecked exceptions are
actually strictly superior.

Here's another pattern that happens with checked exceptions in Java:

    
    
        try {
          doSomeSQL();
        } catch (SQLException e) {
          // do nothing
        }
    

You will see people do this all the time because they don't want to deal with
the checked exceptions. A better catch-clause is:

    
    
        throw new RuntimeException(e);
    

You can argue people shouldn't do the first and they shouldn't but unchecked
exceptions will simply bubble up unless you deliberately swallow it. That's a
way better default. Defaults matter.

~~~
Mindless2112
> _checked exceptions pollute your API and expose implementation details_

Exceptions aren't an implementation detail. They're part of the API contract.
Checked exceptions make this explicit, but people don't like that because
error handling is hard and pretending that errors don't happen is easy.

~~~
merb
I argue against that. because the problem is not that error handling is hard,
the problem is that it is mostly unnecessary. Basically if you would write a
hello world that writes directly to STDOUT you would need to handle
IOException, guess what? it's basically useless to handle any kind of
IOException in that case, because guess what? if your i/o device for output to
display is broken you can only let your program crash. and that is the most
common case how most exceptions are handled. and thats excalty where the k8s
hype is about.

fail fast, start from scratch. you can't handle a network error in your
application, but you can basically kill everything that lost network access to
your database and recreate it where database access is still possible. but you
don't need to do that in your application, your infrastructure should handle
these and thats why most checked exceptions in the java stdlib are basically
stupid. 90% of the stuff reuses exceptions for ease of use but 90% of the
stuff should be runtimeexception and 90% of them should split them up, between
stuff that I COULD recover from and from stuff that I can't (i.e. SSLException
should be split into validation of certificate exceptions and protocol
violations!!! that mostly can't be handled without reconfiguring the JVM!!!! I
mean there is a SSLProtocolException but it's basically useless since it
reports a totally different thing..)

------
flowerlad
The biggest mistake of C# is not having checked exceptions. Your carefully
written program can crash because someone modified a dependency to throw a new
exception. So the only way to make your program resilient against such changes
is to catch the base Exception class, which everyone agrees is wrong (because
of "swallowed" exceptions). In Java the compiler alerts you if someone
modifies a dependency to throw a new exception, which is good.

See long discussion here:
[https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dl...](https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dlang.org)

~~~
sedatk
Arguably, a careless library developer can break your code in infinite
different ways than changing the exception type, even in Java.

I'll get one in a million chance of careless library developer randomly
changing exception types over writing `throws` and `try/catch` statements a
million times even when I don't need to handle any exceptions at all.

------
mcculley
I would prefer there were just a semantic way to find out what exceptions a
method might throw. I develop a lot of Java and often end up just rethrowing a
checked exception as RuntimeException or AssertionError.

A long time ago, I developed quite a lot of code in Ada83. Our team found
having to use documentation to express what exceptions might be thrown led to
many errors. I was pleased when Java came around that this was expressed
directly in the function declaration.

But then it became clear that it lead to a lot of boilerplate.

I would like the throws keyword to just be an indication of what might be
thrown and not require that I catch it.

------
CornCobs
Having used Java somewhat, the biggest pain points I encountered with checked
exceptions was their incompatibility with highly generic code (aka streams).
What if all library functions taking lambdas (e.g. map, filter) all took
throwing functions as parameters (i.e. have an extra generic X argument for
the exception type, like R apply(T arg) throws X) and simply rethrew those
exceptions, AND have the exceptionless functions (R apply(T arg)) be a subtype
of the throwing version so they are compatible? I haven't touched java in a
while so I may have forgotten a thing or 2 about its type system

------
noisy_boy
I've found checked exceptions pretty useful. I can pass context-specific
details to the exception and encapsulate the message formatting to the
exception itself (helps if I'm throwing that in multiple places). They also
allow me to decide the http return code based on the exception. E.g. using
Spring Boot's controller advise, I can map a group of exceptions to be user
errors (say, bad request) and another group to be service errors (say,
internal server error) etc and don't have to worry about where the exception
is being thrown from - it'll return all the details with correct return code
to the user.

------
jayd16
I don't mind Java checked exceptions but of the languages in this style I
think I prefer C#'s Task type. It combines a promise with a simple IsFaulted
boolean and a way to rethrow the caught exception at your discretion (or it
will throw if you accidentally access the result getter).

You can use catch block style with typed exception handling or simple boolean
checks depending on the situation and what you prefer.

------
tomohawk
Java has checked exceptions, unchecked exceptions, and errors.

Some checked exceptions, such as InterruptedException, should really be
something else. I've very rarely seen this exception handled properly by
anyone. Often, a general catch Exception will also catch this, and while the
code will work just fine in most circumstances, random threads will not go
away when they should.

It's a mess.

------
bitwize
Java's biggest mistake was not including lambdas and generics from the very
beginning. Checked exceptions are a feature because they force you to think
about how you will handle exceptions: usually one of die, try again, or try
something else. Forcing the programmer to make these decisions explicitly is a
good thing.

~~~
sedatk
C# had also adopted lambdas and generics post first release, but made all the
right decisions for transition so type system isn't a mess.

------
mcguire
If the exceptions thrown by your code aren't part of the interface, then
pretty much nothing is. That means strong typing is actually Java's biggest
mistake.

This isn't to say that checked exceptions are always used well. (What exactly
am I supposed to do about an exception from .close()?)

------
whitenoice
Most codebases use lombok, you can use @SneakyThrows or
@SneakyThrows(SpecificException.class) -
[https://projectlombok.org/features/SneakyThrows](https://projectlombok.org/features/SneakyThrows)

------
sreque
I'm surprised how controversial this is; checked exceptions are a mistake.
There is a reason C#, Go Swift, Rust, Scala, etc. don't have them, and it's
not because these language authors don't know what they are doing.

Checked exceptions have all the disadvantages of monads:

* Checked exceptions don't _compose_ with each other. You have to create manual witnesses of composition (methods throwing multiple exception types, or wrapping exceptions into new exceptions like a monad transformer)

* Checked exceptions infect the type system, having to be copied everywhere.

However, they have none of the advantages of monads, and more disadvantages
besides. Java the language does not provide any facilities for abstracting
over checked exceptions, and they interact terribly with any design involving
higher order functions or any other higher-level abstraction.

It's time for the java community to admit they got this one wrong and move on.

~~~
Nursie
> Java the language does not provide any facilities for abstracting over
> checked exceptions

Can you explain what you mean by this?

~~~
sreque
I can't write a method like this:

public <T> higherOrderFunction(Function<T> f) throws whatever f throws { }

I can write a method that takes in a function object that throws zero checked
exceptions. I can write a method that takes in a function that throws exactly
one type of checked exception. I can write a method that takes in a function
object that can throw two types of checked exceptions. And so on. But this
involves lots of copy-pasting, which is the opposite of abstracting.

Checked exceptions are not first-class citizens in the language (see
[https://en.wikipedia.org/wiki/First-
class_citizen](https://en.wikipedia.org/wiki/First-class_citizen)). Unlike
return values, I can't, for instance, in the general case assign the possible
checked exception thrown by a method to a variable without losing type
information. To do so would require sum types, a feature which most popular
languages don't have.

On the other hand, if I create a class like IO<T>, which represents a possible
return value of T or an IOException, then that is first class in the language
and I can do anything with it that I can do with any other first-class value
in the language.

~~~
Nursie
So you effectively want -

class ThrowingFunction<T, R>{ public T execute() throws R; }

public <T, R> higherOrderFunction(ThrowingFunction<T,R> f) throws R { }

Honestly I have no idea if such a construct is possible. You can write a
method that throws a superclass of exceptions.

At this point though I'm going to say I also don't see the utility. By the
time we're getting so abstract we're also getting into code that can be quite
hard to reason about and debug.

Exceptions are first class in java AFAICT, they're just objects. You can store
anything in them and pass them around freely.

------
desertlounger
I write a lot of Java, and like checked exceptions. The problem is rather the
opposite, basically all of the concrete exception classes that you might think
to use (e.g. IllegalArgumentException) are unchecked!

------
ncmncm
Sure and they are a big, big mistake, but biggest? I can name bigger ones.
Default virtual is quite the whopper, for example.

~~~
implements
Looking it up, “virtual” seems to mean “capable of being overridden by a
subclass”. Rather than being a whopper, wouldn’t that be the natural thing for
a pure-ish OOP language?

~~~
koenigdavidmj
If you know that a method isn't virtual, calling that method is just a
function call with an extra argument for 'this'. If it's virtual, you have to
look it up in a vtable, probably dereferencing two pointers in the process and
making it harder on your instruction fetcher.

Java makes a lot of common classes final (e.g. String and Optional) so that it
can avoid this. As I understand it, it's also got a lot of pixie dust to
predict this some of the time, but then you can't rely on it.

~~~
hinkley
When the JIT was finally smart enough to handle lookups better, they didn’t
change the types.

Final classes encourage the creation of utility classes, which can often
become a problem.

------
franzwong
I also see people saying they miss checked exception because developers always
forget to check the returned error code...

------
storedbox
Checked exceptions are hardly Java's biggest mistake.

------
kanzenryu2
It's easy to see that checked exceptions are bad... no other language tried to
copy the idea. A worthy experiment, but that's all. What else did not get
copied? Classloaders.

~~~
ragnese
Actually, PHP has checked exceptions exactly like Java

~~~
kanzenryu2
Well, that I did not know. But I'll take it as the exception that proves the
rule if only one language copied it.

~~~
ragnese
It's certainly not an endorsement of checked exceptions. Just an FYI. And,
further, PHP is basically just copying every feature they can from Java for
the last several years.

------
tealpod
My biggest complaint with Java is Generics. I am not against Generics, I don't
like the way they were implmented in Java and they copy/pasted the same shit
to C#.

