79 comments

[ 4.2 ms ] story [ 139 ms ] thread
Are there guidelines for using exceptions in a correct way?
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.

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.
Agreed, I overlook that case. I've done exactly that in Java and Ruby.
In large, layered software projects, the best solution can be "catch and wrap". In that case, the wrapping exception provides information about what went wrong, and the wrapped exception says why. For example, a CouldNotLoadImageException might wrap a JPegDecodingException or a CouldNotConnectException, depending on what went wrong underneath.
A book even! I linked it below, as a level 1 comment.
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."

That's what I was thinking. Maybe once in a while I just want to catch a parsing error specifically (like when parsing a string into a number) and then other times I just want to hear when anything goes wrong with some operation. That's not necessarily a misuse of exception handling.
The diaper pattern is a well-know anti-pattern. But the idea that all exceptions are thus evil is a giant leap of logic that is hard to swallow.
At least in Java, exceptions are often very granular, when you actually don't care.

Let's say you want to read a file. You may get an exception for malformed paths, permission errors, filesystem errors and so on. It's unlikely that I will recover from any of these errors during runtime. The only thing I really can do is log the error, inform the user and send a report.

My usual use case involves adding that file to a "retry later" queue. Just logging it and hoping for the best doesn't cut it if you really need to read the file. But maybe I'm an outlier. Most files I see have to be processed, either now or later, without any manual interaction.
Very granular, and not hierarchical enough. What is ironical for Java. Going either way would fix the problem. There's also a lack of support for them in generics.

The checked exceptions problem on Java is one of overall language and libraries design, not inherent on the checked exceptions idea.

You're right, in fact I'd argue that checked exceptions are a very good idea. A checked exception is java's way to say: Here is a side effect, take care of it. The problem is that it can't be enforced consequently, partially because Java allows objects to be null. Otherwise checked exceptions would be akin to monads.
Diaper pattern - Never heard that before but I like it and it's accurate.

I prefer to catch Exceptions that I know how I want to handle and handle them accordingly. For other exceptions, I prefer to let them fly. If and when they happen, I can then understand the scenario and implement the desired logic.

This is especially true with Java 8's lambda syntax and method references. Because a Java function's signature includes its checked exceptions, implementing a functional interface can require lots of verbose nested try-catches.

e.g. rather than being able to write:

  foodOnPlate.forEach(this::eat);
You end up having to write:

  foodOnPlate.forEach(food -> {
      try {
          eat(food)
      } catch (Barf e) {
          // handle?  rethrow as unchecked?
      }
  });
This library does a pretty god job handling that case:

https://github.com/diffplug/durian/blob/master/test/com/diff...

Yeah, Durian pretty much saves my life.
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.
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.

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.
When a program is shutting down, its threads are interrupted.
Well, I agree the correct behaviour is to either swallow or throw, but I'd argue it is more like:

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

    try {
        Thread.sleep(1000);
    } catch (final InterruptedException e) {
        LOG.info("Woken up early", e);
    }
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.

> 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?

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.

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

What is the difference between an exception and an "out of band" return of an error value?
Exceptions must be handled somewhere. Out of band error values are dropped on the floor by default, which is very convenient (if you don't care whether your software works), and many programmers take advantage of it (without realizing they have done so).
I'm not sure what you mean by error values being dropped on the floor by default. In Go or languages with ADTs you would have to consciously ignore an exception. Languages where pattern matching is prevalent force you to deal with the exceptional case to unpack anything useful from an Either for instance.

And if the study is to be believed, checked exception are almost always dropped on the floor, just with more boilerplate and ceremony.

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.

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).
You have to return codes everywhere through your program, including all libraries.

Exception, on other hand, just happen and get caught in a few places.

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.

> 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 :(

There are almost always the possibility of exceptions: you can get NullPointerException, OutOfMemoryError, ArrayIndexOutOfBoundsException, etc. There is a log of exceptions that derive from RuntimeException.

You must always assume that code in the exception handler can fail unless you have written every single line and it allocates no memory at all.

All these exceptions are basically errors in your code, which is the reason they are runtime exceptions in the first place. The API needs a way to tell you that you produced a bug (because the type system, programming environment, etc. isn't advanced enough to prevent that case), but it doesn't have to bother you with writing code to handle that case: If it happens at production time you have a bug. Fix it. Don't try to work around it.
Not all. Some of them are related to execution environment of an application, not to your code, and in this case developer may actually have something to do to adapt to the environment, rather than simply crashing.
> Not all. Some of them are related to execution environment of an application, not to your code

Let's say "more or less all": Yes, if there's an OOM because the system has not enough memory at all you maybe have to tell the user what to do instead of just crashing because the environment failed you, but in almost every other case the moment you accept the environment as parameter it becomes part of your code: If someone gives you a null object you can throw an IllegalArgumentException and show the calling program that it misused your API, but if you accept null and then call some method without checking for null the NPE you get is your bug, not that of your caller.

Edit: Rewritten to make my point clearer.

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.

The complaint is that it adds a lot of noise as people are forced to write handlers for exceptions they're not interested in, and these handlers are typically empty.
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.

I think the problem isn't so much with checked exceptions as it is with designing a throws clause for interface methods that is both useful and general enough.

The point of interfaces is to abstract from the concrete implementation. But that also means that the API designer cannot possibly know what types of exceptions concrete interface implementations can throw.

You're left with the choice of either declaring "throws Exception" on all/most interface methods or using unchecked exceptions.

I don't typically find much value in an exception, checked or unchecked, that is passed up the chain more than one step. The ability to intelligently handle an exception other than by logging/swallowing it decreases very quickly with every step up the call chain. If I don't know what to do with an exception in a particular method it is highly unlikely the caller of that method will have more of a clue.

That's why I tend to catch all exceptions and then either produce a fallback value, or signal back to the caller by returning an Optional (if the only useful thing to tell it is that it wasn't possible to produce a value) or by throwing a new specifically tailored exception and making that checked. In either case, the caller must be explicit about what they want to do when things go wrong.

There are few specific cases, when you actually can do something after multiple returns, and they belong to RuntimeException hierarchy.

I've seen good example of it just few hours ago, when my IntelliJ WebStorm (Java application) signalled, that it doesn't have enough memory (OoME) and suggested to update Xmx config setting and restart. Apparently, such exceptions should be handled at the root of execution stack, but sometimes, like in this case, there are ways to handle them with minimal damage.

That's why you allocate every resource in a try-with-resources block, and let the exception fly all the way up to the top level of the stack, where you log it.

Everything is unwound, everything is safe, special logic and wrapping not required (though wrapping can be valuable if there is information to add along the way).

Nah, they're pretty much a flaw. The idea of having compile time checks for exceptions is a good one, but the assumption that the callee understands what should be checked/unchecked by the caller is... not well supported by the evidence.
Go's approach requires specific code to handle errors in each and every function, and it's very easy to forget or introduce a bug in the propagation of the errors (or to ignore one altogether).

So I wouldn't use that as proof that the world has settled on global "stop the world" exceptions vs checked exceptions.

Rust is another one that requires specific Result<DataType, ErrorType> to be passed back to a calling function, that either contains the return value or an error. But unlike Go, it's very difficult to ignore the error entirely, but it does require code to handle and propagate errors up the stack.

Although both Go and Rust also have the concept of a panic, that does stop the world (or at least the current thread, unless the panic is captured but that's another story...).

It's pretty difficult to ignore an error entirely in Go as there's a syntactical red flag everywhere you do it.

The problem with Go's error handling is that it doesn't play nice with using functions as part of larger expressions.

It's my favorite thing about Go.[0] Even in C++ I use error values instead of exceptions, regardless of RAII, so it was a natural transition to Go. Although I'd like to see what a C-style language with Common Lisp-style error conditions and restarts would look like. Ruby has a little bit of that functionality with `redo` and `retry`.

[0] Followed by multiple return values, duck-typed interfaces, automatic pointer dereferencing, channels, and a rich standard library. Sorry, this didn't begin as a rah-rah Go post.

The problem I have with Go's error handling is that it makes you choose: Either you write a function that can return errors or you write a function that can be used as part of a larger expression. It can never be both.

  result := f() * g()
becomes

  x, err := f()
  if err != nil {
    //handle f() err
  }

  y, err := g()
  if err != nil {
    //handle g() err
  }

  result := x * y
Even without exceptions I can imagine to have something like

  result, err := f() * g()
  if err != nil {
    //handle err
  }
Of course that raises a lot of other questions if functions can have side-effects (but possibly no more so than shortcut logical operators)
You must choose, but there's nothing stopping you from collecting errors in the background and creating a monad. Return a maybe value and expose a way to access the error.
What's stopping me from doing that more often than I do is that it's too much work.

Collecting errors is just part of it. You also have to make sure nothing that has side effects gets called after the first one.

Without language support this is tortuous and error prone.

"...involve complicated exception handlers..."

You mean delegating exception processing to a separate code path, a la the Strategy pattern? Or just straight up catch blocks?

The case I remember was the exception handler was trying to do file I/O and got a second exception that was handled somewhere up the call stack. That second exception is the one that was listed in the support ticket.
I think your complaint is simply that error handling code paths are rarely tested, which is true, but is also true in programs that don't use exceptions.
In the context of an organization that has a formal QA department, it is very difficult for the QA folks to make the system throw all the relevant exceptions. It falls to the developers to test those code paths and then there are a million other things that have higher priority.
The stack unwinding was such a sad thing to me. Reading about CL condition system made me smile widely. You have more data to treat the exception. While reading the abstract, suddenly the word Exception felt absurd. It's rarely an exceptional condition, it's really a parallel planned error checking.

Also, being from the Java era, an Exception was mostly a boring way to ensure error checking, while lispers did use exception as cut / tree jmp to provide solution values to an algorithm.

ps: funny today youtube suggested this https://www.youtube.com/watch?v=zp0OEDcAro0

The closest I can find to that quote is the following, from the section called "Dead Programs Tell no Lies":

"when your code discovers that something that was supposed to be impossible just happened, your program is no longer viable. Anything it does from this point forward becomes suspect, so terminate it as soon as possible. A dead program normally does a lot less damage than a crippled one."

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.

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.
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.
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.
Better yet have monitors/counters...
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.
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.

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

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.

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.

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.
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.
See jcrites comment here 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...)

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

Those aren't exceptions, those are bugs.

Exceptions happen all the time because of operational errors.

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.

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

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?