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.