
Java Exception Handling - ingve
http://neverworkintheory.org/2016/04/26/java-exception-handling.html
======
johnwatson11218
The pragmatic programmer book has a great quote about exceptions. Something
about how once an exception is caught your stack starts to unwind and a new
program is now running on your system. Can't find it right now.

The worse production bugs I have seen involve complicated exception handlers
that generated a second exception while trying to write to the filesystem or
something. In that case some other exception handler now starts to execute
further up the stack. It really is as if a new program is now compiled and
installed on your production servers. A new program that has read/write access
to the production db. A program that most likely was never executed in QA and
has never been seen by any person.

It is a bit hyperbolic but I have never seen an exception handler over four
lines long that did not contain a bug.

My attitude towards exceptions is like a fire alarm. If I hear a fire alarm it
means one thing and I have one action to take. Any notion of providing
different pitched fire alarms to encode information would be absurd and
dangerous, that is how I view exception hierarchies.

It is my understands that the Go language from google decided not to include
this language feature.

I have heard that Java's checked exceptions are now seen as an important,
failed experiment.

~~~
sgift
> I have heard that Java's checked exceptions are now seen as an important,
> failed experiment.

Sadly, this is what people often say these days, I still think they provide
far more value than unchecked exceptions. I really liked it when I didn't have
to provide any exception signatures and didn't have to think about where to
catch them. I didn't like it so much anymore when unchecked exceptions started
crashing my code, because there were exceptions thrown at points I never
expected them to be thrown.

Either no exceptions or checked exceptions, but unchecked? My head :(

~~~
specialist
I still don't understand the complaint against checked exceptions.

I've concluded that unchecked exceptions (above the runtime) and exception
chaining are painful artifacts necessitated by using overwrought frameworks
like Spring, J2EE, JPA, etc.

Most useful anti-checked exception TL;DR I've gotten is "What do you do when
your code can't act (recover) on a checked exception, like some library fails
on I/O?"

My answer remains "I handle it." After demo'ing my code, buddies conceded that
my strategy works because I'm coding "close to the metal". In that case, it
was an ETL/workflow engine (framework) that used hand-coded state machines (eg
get next, data error, retry, restart).

Looking forward, I'm keen to learn Erlang and Elixir. I have the impression
they would provide for free all the error handling and recovery I
painstakingly hand-coded.

~~~
int_19h
Checked exceptions don't play well with high-order functions, and basically
anything else where one piece of code is calling into another piece such that
it doesn't know what it is at compile-time (dynamically loaded extensions, DI,
UI event handlers, and even plain old virtual methods in many cases).

Basically, at those boundaries, you either have to say that the called code
can throw anything - but then what about your own contract? you will either
have to swallow everything; or rethrow everything, making it a part of your
contract; or wrap everything in an exception type that you declare vas part of
your contract, which is really a disguised version of rethrowing that doesn't
add any meaningful value. Or else you say that the callee cannot throw
anything, and then it is forced to swallow exceptions at the boundary.

Here's a very simple example of how it affects design: try implementing Java's
List<T>, or even Iterable<T>, on top of a file. Something really simple, like
one string element per line.

You'll find that it all works, except for error handling - because every read
can fail, and neither List.get() nor Iterator.next() let you throw an
appropriate exception type.

In contrast, in .NET, File.ReadLines will happily return an
IEnumerable<string>, which will throw IOException when a given line couldn't
be read - because IEnumerator is not limited with respect to what exceptions
it cannot throw.

To properly deal with this problem, you need value-dependent types. Then you
can do things like write, say, a generic implementation of map() that takes
any random sequence, and throws the same exceptions that said sequence can
throw when it's iterated.

------
wellpast
This is flawed thinking.

I'd say in many cases it is perfectly desired to either swallow or re-throw
exceptions. As just one example, many business rules can be correctly
implemented as best-effort and if some resource (network or service) is
unavailable simply let the failure propogate, or log, or backoff and retry
some other time.

And most certainly but in fewer cases more robust handling is called for. Even
if only 10% of exception handling is in this category, then try/catch is still
a valuable tool.

I find it really faulty to reason that "this feature is only used 'correctly'
10% of the time, therefore it's a failed feature."

~~~
wellpast
Here's a common pattern for sleeping on a thread you do not expect to be
interrupted and both are equally valid and fine ways to implement it:

    
    
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         throw new IllegalStateException(e);
      }
    
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         // never happens
      }
    

In other words, the _correct_ handling is to swallow or throw. The former is
likely preferred by those who do assertion-based coding. The latter for people
that prefer to discover their bugs in other ways.

~~~
coredog64
One should never expect a thread not to be interrupted.

When I see code like the latter in PRs it drives me nuts, to the point that I
start building up a case to have the knucklehead that does it moved off onto
another project.

~~~
wellpast
Why should one always expect a thread to be interruptible? If I don't run my
thread within an Executor or some other framework that could interrupt my
thread, then I should be able to consider that an illegal state.

~~~
BrandonM
When a program is shutting down, its threads are interrupted.

------
michaelfeathers
The great thing about exceptions is that they allow you to put arbitrary
distance between the point of detection and the point of handling.

The problem with exceptions is that they allow you to put arbitrary distance
between the point of detection and the point of handling.

------
norswap
See this article ([http://norswap.com/checked-
exceptions/](http://norswap.com/checked-exceptions/)) on why exceptions are
generally misused.

------
golergka
A lot of opinions about error handling in this thread, but I don't see the
main thing mentioned: difference in requirements of different projects and
subsequent difference in technical strategies.

Some applications need to fail fast, fail hard, and restart automatically.

Some applications need to present any failure or error condition to the user
and give him power to decide.

Some applications need to be restored to some sensible state at all cost.

Needless to say, all possible error handling strategies, from C return codes
to Java checked exceptions, have their fair use cases; discussing which one is
better and which one is worse without specific project in mind, at least as an
example, is completely pointless.

------
johnwatson11218
The best use of exceptions in java that I have seen in production is when an
exception is used to roll back a database transaction or when the web layer is
able to catch all exceptions, log them, and then redirect the end user to a
nice looking page telling something went wrong.

Another anti-pattern is subclassing Exceptions to have things like
"UserNameInUse" exceptions during a sign up path to indicate that a selected
username is not unique in the db. This becomes a poor man's message passing
implementation. Using an exception to signal that is very inefficient as the
exception has the entire stack trace as part of its data.

~~~
barrkel
Using an exception to signal a semantic error is only expensive if the
language implementer has made that expensive.

Technically, all the exception throw needs do is pop stack frames until it
finds a handler. Getting a stack trace is optional, but keeping track of the
return addresses (pushed by the CPU's call instruction) is sufficient to
materialize that on demand. An array of integers isn't expensive either.

I don't believe transactional programs using exceptions for rollback is an
anti-pattern. It's not the best pattern - a transactional language, with
transactional data structures, is much harder to get wrong - but it's not
worst, not by a long shot.

~~~
johnwatson11218
sorry - I didn't mean to indicate that using transactions to signal a roll
back is an anit-pattern. I started out that comment listing good things I had
see exceptions used for. Then that example of the userNameInUse exception came
to mind. I realize my choice of words made that less than clear.

------
rodionos
The volume of logging in production systems is such is that it is impractical
to act on individual exceptions. You would typically send log events to a
centralized logging service or have an agent-based code monitor logs locally
for well know sequences or error codes. And that's where Java exceptions can
be improved - a built-in facility to assign error codes so that downstream
tools can handle exceptions based on a rule book which takes application-
specific error codes into account.

~~~
ivan_gammel
If you don't want to use regular expressions for matching exceptions, you can
simply calculate hash code of exception class name and, possibly, of it's
cause and message and pass it downstream.

------
jdiscar
I think this is fine. Java is verbose as it is, it would be even worse if
Exceptions were handled "as intended" and every Exception type was treated
individually. Log and move on can be appropriate in some cases. And even if
80% of the time you're ignoring all Exceptions, the 20% of the time you have
specific code could be very, very important.

------
jcrites
Java exception handling is very successful, and does exactly what I want as a
system designer.

The article objects to the fact that applications handle many exceptions by
logging them and giving up. What's wrong with that? That's perfect. For the
class of exceptions that I _can_ just log and give up, that's often what I
want to do. If I had an open TCP socket to a remote host, then I can't
"recover" from an exception indicating that the connection was broken.
Instead, I just want to tear down everything related to that connection and
carry on.

Exceptions can rarely be recovered from at the level of abstraction where the
exception occurred. For example, I can't repair a socket that's been broken. I
cannot retry the request at that level - it might not be idempotent; the
socket processing code can't know. The connection pool doesn't know. The HTTP
client might or might not know. However, I _can_ recover from the exception at
a higher level -- at the level of the code that understands what the task is,
it might retry the top level processing job, which might invoke a call on a
service client, which will invoke a call on an HTTP client, which will invoke
a call on an HTTP connection pool to open a new TCP socket as needed. This is
exactly the pattern that we see commonly in Java applications: a higher level
module calls a lower level one, and then variously logs exceptions and tries
again (possibly with exponential backoff over multiple attempts), or logs them
and give up, or does something else.

You don't really "repair" or "recover" from exceptions. You mitigate them by
handling them and carrying on with the job. To achieve this, you unwind up to
the level where it makes sense to retry or abort a task. When you retry, you
retry from the right level of abstraction needed to start over.

The extremely valuable property that exceptions have, that they give you, is
confidence about what specifically went wrong. They constrain it. If I try to
interact with a socket and I get an IO exception, then I know the error is
constrained specifically to that socket. The rest of the program is working
fine, and I can either try to recover from that error (sometimes possible), or
close the connection and open a new one, or give up processing the task.

Exceptions are also valuable because you don't have to add error-handling
logic at each layer in an application. Many layers can be ignorant or agnostic
of the exceptions that will propagate through them, and that's a good thing
because it reduces coupling. These layers might be inserted between the high-
level logic that understands task processing, and that will make the judgment
call about retry/backoff/give up, and the specific code that can fail and
throw an exception. I don't want all layers of code to _have_ to care about
exceptions that might pass through them; they can't and shouldn't do anything
about it.

It is valuable to be able to build abstractions and build code that exceptions
_pass through_ , without handling them. You might add logic to handle them
later, or you might do so at a higher level, or a lower level.

~~~
nercury
Retrying on any exception might be wrong thing to do. For example, the I/O
error many levels bellow might happen for wildly different reasons, and
handling them all the same way might be wrong. If not, then the correct
behavior of the project depends on exceptions originating from different
modules being handled the same, and that _increases_ coupling.

~~~
jcrites
You make a good point and I agree. However, code also doesn't exist in a
vacuum; when it's first written it's not perfect. Systems are built
iteratively as the problem becomes gradually more understood. The wonderful
thing about exceptions is they fit well into this evolution process. I can
wrap the top level application processing step in a try/catch block, and I can
be confident that we have a plausible starting point. In the very least, I can
be confident that I will detect all reasonable failures, and I can be
confident they won't spill over into anything else the application is doing.
It's very difficult to crash a Java application that's designed to be robust.

If any of my low-level exceptions bubble up into the top-level catch block,
when we're expecting to handle all exceptions at lower levels, then I have the
ability to do something reasonable in my catch block: abort the task, emit
metrics, and log the full stack trace as part of my "unexpected exception"
routine. That's really useful: it's one way to discover exceptions that the
system ought to handle but isn't handling yet. You might write one of these
catch blocks with the expectation that it "should never happen", but if it
does we'll detect it and supply a reasonable default behavior.

For example, if my application is a microservice, then I will wrap each
microservice operation in a try/catch block, and in the catch block I will
include logic to convert the exception into an appropriate failure return code
from the operation. Or even better, my service framework will handle this for
me -- that's an example of the decoupling that exceptions provide as an
abstraction. If any microservice operation throws an undeclared exception,
then the framework can surface it as the equivalent of HTTP 503. We might set
the expectation that all exceptions should be declared exceptions, but if we
ever miss one and have an undeclared exception, then the framework can return
an appropriate error response.

The right thing happens by default: we don't need detect-and-propagate logic
in each call frame. Some of those layers could also catch and retry the
exception, or catch it and refine the exception type, or just catch and log it
and emit task-specific log entries or metrics. For example, perhaps I have a
try/catch block around some logic that calls S3. A catch block around the S3
calls can emit metrics describing the failure rate of calls to S3, which are
useful to alarm on, and to examine distinct from the overall failure rate of
my microservice operation which might call other services too. This is an
example where a simple catch-observe-propagate-or-rethrow adds meaningful
value.

At the same time, those failures could happen for many different reasons.
Perhaps my credentials have expired, and so I'll see a 100% failure rate with
that step: emergency. Perhaps I'm using a presigned URL, and it's past the
expiration time. Perhaps there is a random transient failure. Perhaps the file
I'm uploading is too big, or perhaps its checksum doesn't match, and so on. We
can start with a basic naive catch block around S3 and refine it over time to
handle these cases if we observe that it's relevant to do so.

Exceptions are an excellent foundation for confidently evolving system
behavior in these sorts of ways. Plus, I can implement this evolution in the
most convenient place for me. I don't have to go to a lot of trouble propagate
all down the call stack to where it's thrown; that code can pass it on without
knowing what it is. Yes, any of these exception handling blocks is coupled to
the exceptions it catches, but higher level blocks aren't, and code in between
can pass them on without understanding them -- that's valuable.

------
pacala
There are two kinds of exceptions. A bug in the data or a bug on the code. The
only action to take is to pass detailed info to the user and/or the coder. Fix
the bug and try again beats any precanned exception recovery code one might
waste time implementing.

~~~
sgift
See jcrites comment here
[https://news.ycombinator.com/item?id=11842726](https://news.ycombinator.com/item?id=11842726)
for an example where neither the code nor the data has a bug. Broken sockets
are simply a part of distributed systems the code has to deal with.

(If you feel that "the network should be reliable" is a valid answer, see:
[https://en.wikipedia.org/wiki/Fallacies_of_distributed_compu...](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing))

~~~
pacala
We should classify exceptions by the _action_ required to fix them, not by the
circumstances in which they appeared. "DivideByZeroException" is useless,
"User error: 31st of February is not a date" or "Internal error: count is
zero" are a whole lot more actionable. Java's menagerie of exceptions is an
anti-pattern.

I grant you that there is a third class of exceptions, "remote unavailable",
for which the error handling is to retry X times, then promote to "system
broken" and let the user retry later.

For all intents and purposes, "system broken" is a better name for "code
error", covering also cases where the DevOps team should fix the hardware or
dependencies on third party systems. [Configs are funnily written code].

Often times the "retry X times" logic is wrapped nicely in one's RPC
infrastructure of choice, so what the app developer has to deal with is either
"user error" or "system broken".

------
guard-of-terra
Exceptions are maddengly, insanely efficient in some settings, such as making
a server handling multiple requests at once.

They allow you to confine errors on any single request to that request. Many
languages/platforms suggest you to use multiprocessing for that.

Also helps that servers usually have well-understood failure modes (DB error,
data validation error, network failure) compared to other kinds of software
(e.g. a missing file may lead to a whole investigation there)

For e.g. desktop programming, exceptions are kinda less useful.

~~~
orf
> They allow you to confine errors on any single request to that request

I don't understand this, returning error codes has the same effect. In fact
you can do that with most forms of error handling, short of segfaulting?

~~~
sgift
The problem usually is: What is the error code if your return type is int?
Sure, you can define all negative ints as errors, or you provide a reference
which is set when an error is encountered and so on, but that ends up being
just another type of "very ugly".

At the moment, I'm mildly intrigued by Rusts result types which force you from
the start to handle "all cases". They strongly remind me of checked
exceptions.

~~~
kilink
> The problem usually is: What is the error code if your return type is int?
> Sure, you can define all negative ints as errors, or you provide a reference
> which is set when an error is encountered and so on, but that ends up being
> just another type of "very ugly".

Many languages support multiple return values, algebraic data types, or box
types, allowing you to return an error value out of band.

~~~
sgift
What is the difference between an exception and an "out of band" return of an
error value?

~~~
kilink
I was addressing your specific assertion with regard to encoding error
conditions as reserved values in your return type's codomain, which I agree is
ugly.

One benefit of not throwing an exception is that you can maintain referential
transparency, which exceptions generally break. You can't substitute a
function call with its value because throwing an exception will have different
effects depending on the context, which consequently hinders the composability
of such functions.

Exceptions are also generally not type-safe, e.g. the return type of a
function tells you nothing about what exceptions it may throw. If you go the
checked exception route like Java, you do get some degree of type safety, but
you do so at the expense of higher-order functions, which can't reasonably be
expected to know about the specific exception types its arguments may throw.

On the other hand, ADTs are composable. They work for functions that aren't
defined for some inputs (maybe an input type that can't be constrained by the
type system). They avoid bugs because they force the caller to deal with the
exceptional case (unlike returning null, a sentinel value, or throwing a
RuntimeException), but without the boilerplate of exceptions, particularly in
languages with pattern matching. The caller gets to decide when, if, and how
to handle the exceptional case.

~~~
sgift
Thanks for the thorough explanation. I agree that ADTs are less ugly due to
reduced boilerplate and the composability, if your language of choice has
exhausting pattern matching (I really don't think "I don't care if my program
works, let it crash" is a good default, so non-exhaustive pattern matching is
out for me).

------
barrkel
There's a lot of misconceptions around errors, and this article isn't free of
them either. Java's checked exceptions feature derives almost entirely from a
misconception around the nature of errors: that errors should, as a rule, be
handled. Only a small subset of errors should ever be handled, usually quite
close to the site of the error - an unwound stack seldom has enough
information to anything useful other than log, inform client, or abort. If the
error is to propagate, there's little win in forcing the whole call tree to
need to know the details, since the nature of the error conveys less and less
information the further away it is from its occurrence - context is
necessarily lost through abstraction.

Errors can be grouped into 3 categories: programming mistake, semantic error
and non-deterministic error.

The first category are bugs that are discovered through sanity checking and
defensive programming - conditional code that is only invoked in the case of a
mistake and is always avoidable. Depending on the environment, a whole program
abort or restart might be a valid response; a localized response (like
handling an exception) isn't going to be useful. Fall back to the event loop /
request-response loop, log and continue usually suffices for non-critical
apps. Checked exceptions are not a good idea, but that's OK - Java uses
unchecked exceptions for these, even if not all libraries do the same.

The second category are semantic errors, typically where a client or end user
has tried to do something invalid, and something like exceptions come in handy
to unwind a partial operation, a bit like rolling back a transaction. Starting
out with optimism but rolling back once a problem has been discovered isn't an
unusual or technically flawed approach; exception handling is perhaps an
error-prone way to do it, but it is a way. And it's a case where localised
exception handling again isn't a good idea. This is a problem area in Java, as
far too many applications use checked exceptions here.

The third category is non-deterministic errors: something outside the world of
the program didn't conform to expectations. The other side of a network socket
disappeared; a file was deleted; a device was disconnected; etc. You can't
detect the error before attempting the operation, and the resulting error is,
in a way, a type of return value - unexpected information - from the attempt.
Fixing the problem is almost certainly only likely to be successful if it's
local (hacks like lazy creation of files, automatic reconnects of sockets,
etc. aside). Propagating the error is fine if it can't be fixed locally - most
code isn't written to have alternative strategies for these scenarios - but
forcing callers to handle this specific type of error - that's not terribly
useful. Again, checked exceptions - specifically, typed checked exceptions -
not giving up the benefit they promised. The fact that something might fail in
a non-deterministic way is useful information at the API level, but it also
violates modularity. The "fix" in Java is for every module to throw its own
hierarchy of checked exceptions, which, of course, is no fix at all: now you
have even less prima facie information, and useless exception handlers and
exception specifications proliferate until the meaning behind them is lost.

Other languages that use option types to propagate errors work fine for the
third category; they usually have a different scheme for the first category;
but on the second category, they are usually silent, and that's a bad thing in
my opinion. Something exception-like is very valuable in the absence of strong
tools for transactional modifications of program state. You shouldn't take
away exceptions without providing a solid way of throwing away partial work.
Erlang - tear down the process - that's OK; functional languages - immutable
state necessitating immutable, persistent data structures - that's OK;
something like Rust - well, it's going to need panic for more than just the
first category.

~~~
narag
_There 's a lot of misconceptions around errors_

No surprise. I've seen books that fail miserably explaining exceptions, some
of then using wrong code for the examples.

Now there is also people that _opposes_ the concept of exceptions. Not sure
what the article conclusion is:

 _The only conclusion I can draw from this is that exception-based error
handling has failed to achieve what its creators intended._

Is he talking about mandatory checked exceptions or any exception in general?

------
the_arun
Are there guidelines for using exceptions in a correct way?

~~~
jacques_chester
My thinking would be:

First, don't catch exceptions unless you really can handle them. Swallowing
and emitting a log line is _not_ handling an exception.

Second, and this is a corollary, only catch the exception classes you _can_
handle.

The classic antipatterns of swallow-and-log or swallow-and-convert are very
frustrating.

Swallow-and-log means that you don't see the true state of the world upon
exceptional conditions arising. It is _particularly_ harmful for gutting the
usefulness of acceptance and unit testing.

Swallow-and-convert means that you deny the rest of the stack a chance to
properly handle the exception. You also suppress the information which checked
exceptions provide to consumers of your method, converting something that can
be solved at coding time into runtime error lotteries.

Edit: per the comments below, my meaning for "swallow-and-convert" is the
practice of turning meaningful exceptions into `RuntimeException` because it's
"too hard" to add exceptions to consuming method signatures.

~~~
sorokod
There are cases where 'swallow-and-convert' is a reasonable thing to do. This
often comes up in multi layer architectures where an exception from a lower
layer makes no sense to a higher level and some sort of semantic
conversion/translation is needed.

~~~
jacques_chester
Agreed, I overlook that case. I've done exactly that in Java and Ruby.

------
mlvljr
Exceptions!

There is a book solely dedicated to the topic:
[http://www.amazon.com/dp/0131008528/?tag=stackoverfl08-20](http://www.amazon.com/dp/0131008528/?tag=stackoverfl08-20)

Had it mentioned (together with another only-one, but fpor ASP) in an
[http://programmers.stackexchange.com/questions/14831/how-
to-...](http://programmers.stackexchange.com/questions/14831/how-to-teach-
exception-handling-for-new-programmers) answer, which the proud professionals
then dewnvoted and eventually deleted :)

------
noodnik
Most of the time, checked exceptions remain an unused language feature,
because:

    
    
      A.) String to Integer with null handling 
          for HTTP GET/POST transformation isn't
          worth the effort, because you're users
          are drunk idiots, and they won't 
          pay attention to your carefully stratified
          error messages anyway. So just tell them
          to sober up, straighten out, and fly 
          right, and barf a hideous, uninformative 
          stack trace at them to scare them off
          until they go away, and come back in
          the morning.
    
      B.) Developers are so rushed to just get shit
          done that NullPointerExceptions, 
          NumberFormatExceptions, 
          ArrayIndexOutOfBoundsExceptions and so
          forth, are useful enough, and probably
          don't need much more differentiation
          anyway. As middleware developers, we're
          really not dealing with anything too
          exotic, and so we mostly don't need to
          re-interpret things like sensor or transducer
          state, or spin dial knob rotation angle,
          or leaf spring load, or magnetic flux.
    

So, just because 90% of server side web developers don't use it, doesn't mean
it's a failure. It just means that interpreting discete character data from
noodniks on the other side of a keyboard and pointing device, behind a TCP/IP
connection can only get _so_ complicated.

