1. Overview
In this tutorial, we'll look at the implications of catching Throwable.
2. The Throwable Class
In the Java documentation, the Throwable class is defined as “the super-class of all errors and exceptions in the Java language“.
Let's look at the hierarchy of the Throwable class:
The Throwable class has two direct sub-classes – namely, the Error and Exception classes.
Error and its sub-classes are unchecked exceptions, while the sub-classes of Exception can be either checked or unchecked exceptions.
Let's look at the types of situations a program can experience when it fails.
3. Recoverable Situations
There are situations where recovery is generally possible and can be handled with either checked or unchecked sub-classes of the Exception class.
For example, a program might want to use a file that happens to not exist at the specified location, resulting in a checked FileNotFoundException being thrown.
Another example is the program attempting to access a system resource without having permission to do that, resulting in an unchecked AccessControlException being thrown.
As per the Java documentation, the Exception class “indicates conditions that a reasonable application might want to catch“.
4. Irrecoverable Situations
There are cases where a program can get in a state where recovery is impossible in an event of a failure. Common examples of this are when a stack overflow occurs or the JVM runs out of memory.
In these situations, the JVM throws StackOverflowError and OutOfMemoryError, respectively. As suggested by their names, these are sub-classes of the Error class.
According to the Java documentation, the Error class “indicates serious problems that a reasonable application should not try to catch“.
5. Example of Recoverable and Irrecoverable Situations
Let's assume that we have an API that allows callers to add unique IDs to some storage facility using the addIDsToStorage method:
class StorageAPI { public void addIDsToStorage(int capacity, Set<String> storage) throws CapacityException { if (capacity < 1) { throw new CapacityException("Capacity of less than 1 is not allowed"); } int count = 0; while (count < capacity) { storage.add(UUID.randomUUID().toString()); count++; } } // other methods go here ... }
Several potential failure points can occur when invoking addIDsToStorage:
- CapacityException – A checked sub-class of Exception when passing a capacity value of less than 1
- NullPointerException – An unchecked sub-class of Exception if a null storage value is provided instead of an instance of Set<String>
- OutOfMemoryError – An unchecked sub-class of Error if the JVM runs out of memory before exiting the while loop
The CapacityException and NullPointerException situations are failures the program can recover from, but the OutOfMemoryError is an irrecoverable one.
6. Catching Throwable
Let's assume the user of the API only catches Throwable in the try-catch when calling addIDsToStorage:
public void add(StorageAPI api, int capacity, Set<String> storage) { try { api.addIDsToStorage(capacity, storage); } catch (Throwable throwable) { // do something here } }
This means that the calling code is reacting to recoverable and irrecoverable situations in the same way.
The general rule in handling exceptions is that the try-catch block must be as specific as possible in catching exceptions. That is, a catch-all scenario must be avoided.
Catching Throwable in our case violates this general rule. To react to recoverable and irrecoverable situations separately, the calling code would have to inspect the instance of the Throwable object inside the catch block.
The better way would be to use a specific approach in handling exceptions and to avoid trying to deal with irrecoverable situations.
7. Conclusion
In this article, we looked at the implications of catching Throwable in a try-catch block.
As always, the full source code of the example is available over on Github.