Is It a Bad Practice to Catch Throwable

Is it a bad practice to catch Throwable?

You need to be as specific as possible. Otherwise unforeseen bugs might creep away this way.

Besides, Throwable covers Error as well and that's usually no point of return. You don't want to catch/handle that, you want your program to die immediately so that you can fix it properly.

Difference between using Throwable and Exception in a try catch

By catching Throwable it includes things that subclass Error. You should generally not do that, except perhaps at the very highest "catch all" level of a thread where you want to log or otherwise handle absolutely everything that can go wrong. It would be more typical in a framework type application (for example an application server or a testing framework) where it can be running unknown code and should not be affected by anything that goes wrong with that code, as much as possible.

Is it okay that NonFatal catches Throwable?

Note that catching Throwable happens more often than you might be aware of. Some of these cases are tightly coupled with Java language features which may produce byte code very similar to the one you have shown.

First, since there is no pendent to finally on the bytecode level, it gets implemented by installing an exception handler for Throwable which will execute the code of the finally block before rethrowing the Throwable if the code flow reaches that point. You could do really bad things at this point:

try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged, return from finally discards any throwable
return;
}
Result:

Nothing

try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged too, throwing in finally shadows any throwable
throw new RuntimeException("has something happened?");
}
Result:

java.lang.RuntimeException: has something happened?
at Throwables.example2(Throwables.java:45)
at Throwables.main(Throwables.java:14)

But of course, there are legitimate use cases for finally, like doing resource cleanup. A related construct using a similar byte code pattern is synchronized, which will release the object monitor before re-throwing:

Object lock = new Object();
try
{
synchronized(lock) {
System.out.println("holding lock: "+Thread.holdsLock(lock));
throw new OutOfMemoryError();
}
}
catch(Throwable t) // just for demonstration
{
System.out.println(t+" has been thrown, holding lock: "+Thread.holdsLock(lock));
}
Result:

holding lock: true
java.lang.OutOfMemoryError has been thrown, holding lock: false

The try-with-resource statement takes this even further; it might modify the pending throwable by recording subsequent suppressed exceptions thrown by the close() operation(s):

try(AutoCloseable c = () -> { throw new Exception("and closing failed too"); }) {
throw new OutOfMemoryError();
}
Result:

java.lang.OutOfMemoryError
at Throwables.example4(Throwables.java:64)
at Throwables.main(Throwables.java:18)
Suppressed: java.lang.Exception: and closing failed too
at Throwables.lambda$example4$0(Throwables.java:63)
at Throwables.example4(Throwables.java:65)
... 1 more

Further, when you submit a task to an ExecutorService, all throwables will be caught and recorded in the returned future:

ExecutorService es = Executors.newSingleThreadExecutor();
Future<Object> f = es.submit(() -> { throw new OutOfMemoryError(); });
try {
f.get();
}
catch(ExecutionException ex) {
System.out.println("caught and wrapped: "+ex.getCause());
}
finally { es.shutdown(); }
Result:

caught and wrapped: java.lang.OutOfMemoryError

In the case of the JRE provided executor services, the responsibility lies at the FutureTask which is the default RunnableFuture used internally. We can demonstrate the behavior directly:

FutureTask<Object> f = new FutureTask<>(() -> { throw new OutOfMemoryError(); });
f.run(); // see, it has been caught
try {
f.get();
}
catch(ExecutionException ex) {
System.out.println("caught and wrapped: "+ex.getCause());
}
Result:

caught and wrapped: java.lang.OutOfMemoryError

But CompletableFuture exhibits a similar behavior of catching all throwables.

// using Runnable::run as Executor means we're executing it directly in our thread
CompletableFuture<Void> cf = CompletableFuture.runAsync(
() -> { throw new OutOfMemoryError(); }, Runnable::run);
System.out.println("if we reach this point, the throwable must have been caught");
cf.join();
Result:

if we reach this point, the throwable must have been caught
java.util.concurrent.CompletionException: java.lang.OutOfMemoryError
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1739)
at java.base/java.util.concurrent.CompletableFuture.asyncRunStage(CompletableFuture.java:1750)
at java.base/java.util.concurrent.CompletableFuture.runAsync(CompletableFuture.java:1959)
at Throwables.example7(Throwables.java:90)
at Throwables.main(Throwables.java:24)
Caused by: java.lang.OutOfMemoryError
at Throwables.lambda$example7$3(Throwables.java:91)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
... 4 more

So the bottom line is, you should not focus on the technical detail of whether Throwable will be caught somewhere, but the semantic of the code. Is this used for ignoring exceptions (bad) or for trying to continue despite serious environmental errors have been reported (bad) or just for performing cleanup (good)? Most of the tools described above can be used for the good and the bad…

Why does Kotlin Result catch Throwables?

I found a series of tweets about this topic by Roman Elizarov, Team Lead in Kotlin Language Research:

If you need error logging/handling (usually at the top level), catch
Throwable. [...] In fact catch(e: Exception) [is a bad practice]. You either
catch an operation-specific exception you need to handle (e.g.
IOException) or all of them (which means Throwable). I've never seen a
real reason to have catch(e: Exception) in your code. It is a bug
waiting to hit you.

on distinction between unrecoverable Errors and recoverable Exceptions:

Java wanted to make this distinction in 1996 but failed to adhere to
it when the platform grew and scaled. In fact, nowadays it is never
possible, in practice, to tell if the problem is recoverable by its
class. The distinction in the JVM and the whole naming confusion is
just a 25-year-old legacy of the bygone era. No one could have
actually predicted back then how it all works out in big systems.

on what to do with Errors:

Log it, notify support, restart the affected operation/subsystem, etc.
No reason limiting those actions just to Exception subtypes. I've
virtually never had issues with logging or otherwise handling
OutOfMemoryError, StackOverflowError, AssertionError, and others
(barring fatal bugs in JVM which are rare). The fact [that] they were
properly handled in code saved countless hours of support efforts
later. In practice [...] OOMs are often caused by bugs in the code
that is trying to allocate some very big data structures. When this
code crashes with OOM the GC cleans up the memory which lets, at
least, your error-handling code to log it properly.

When should Throwable be used instead of new Exception?

(from comments) The issue that brought this up is that
I need to pass an 'exception' to a
piece of code a coworker is building
if a collection does not get built.

In that case, you might want to throw a checked exception. You could throw an Exception, an appropriate existing subclass of it (except RuntimeException and its subclasses which are unchecked), or a custom subclass of Exception (e.g. "CollectionBuildException"). See the Java Tutorial on Exceptions to get up to speed with Java exceptions.

Why is the Catch(Exception) almost always a bad Idea?

Because when you catch exception you're supposed to handle it properly. And you cannot expect to handle all kind of exceptions in your code. Also when you catch all exceptions, you may get an exception that cannot deal with and prevent code that is upper in the stack to handle it properly.

The general principal is to catch the most specific type you can.



Related Topics



Leave a reply



Submit