Skip to content

Short Tip on Java: throw multiple Exceptions at once

Reading Time: 5 minutes

Exceptions in Java are intended to interrupt the execution flow in unexpected situations. But there might be a need to continue the execution without interruption, still being able to collect all the errors that occurred before.

An abstract example of such a flow can be a Backup Manager. To increase reliability, it might need to save backups to multiple targets. Those can be a local file system, an external file system, and some cloud provider.

The whole flow will consist of three steps, one for each type of backup target. But the execution of these steps is unpredictable. Exceptions may happen at any moment, and the rest of the flow will be interrupted, which is bad, as one broken target may destroy the whole reliability concept.

Of course, Java has mechanisms to prevent interruption. Using try-catch block for each of the steps will guarantee that all the steps will be executed. The only problem here is that at the end of the execution there will be too little information about what went wrong.

Exceptions may be collected to some list and processed afterward. But in some cases, there is no need for such sophisticated solutions. The only needed outcome may be a single exception that will just explain the whole execution and what was wrong during it.

To achieve this it would be nice to be able to chain exceptions like Java can do when the new Exception is constructed. But using try-catch the exception comes already constructed and passed to a catch block as is. So there is no way to attach a cause to it.

Secondly, setting one exception as a cause of another exception may not be suitable for the case with Backup Manager, as, in fact, more recent exceptions are not the causes for the former ones.

Alternatively, Java API provides the following method:
Throwable#addSuppressed(Throwable exception).

The idea here is pretty simple, addSuppressed attaches another exception to the original one as suppressed by the original. Each exception may have multiple suppressed ones, so the hierarchy may be pretty flexible and deep.

Example

It is easier to understand the concept with a real example. Below there is a trivial implementation of the Backup Manager.

As it was mentioned before, there are three types of backups:

public class LocalFileSystemBackup {

    public void save() throws Exception {
        throw new Exception("Local Storage is full");
    }

}
public class ExternalFileSystemBackup {

    public void save() throws Exception {
        throw new Exception("External Storage is not mounted");
    }

}
public class CloudBackup {

    public void save() throws Exception {
        throw new Exception("Cloud storage is not accessible");
    }

}

For this case, it is assumed that all of them are causing failures.

Now, with the Java API mentioned before, exceptions may be chained with the help of a small utility method that will build a chain of those. suppressor is an exception that has been thrown last, and suppressed is an exception that existed before.

private static Exception chainExceptions(Exception suppressed, Exception suppressor) {
    if (suppressed == null && suppressor == null) {
        return null;
    }

    if (suppressed == null) {
        return suppressor;
    }

    if (suppressor == null) {
        return suppressed;
    }

    suppressor.addSuppressed(suppressed);
    return suppressor;
}

With this in place, Backup Manager’s flow will look like this:

public void backup() throws Exception {
    LocalFileSystemBackup localFileSystemBackup = 
            new LocalFileSystemBackup();

    ExternalFileSystemBackup externalFileSystemBackup = 
            new ExternalFileSystemBackup();

    CloudBackup cloudBackup = new CloudBackup();

    // exception to accumulate other execution exceptions
    Exception executionException = null;

    // try to backup to local storage
    try {
        localFileSystemBackup.save();
    }
    catch (Exception e) {
        executionException = chainExceptions(executionException, e);
    }

    // try to backup to external storage
    try {
        externalFileSystemBackup.save();
    }
    catch (Exception e) {
        executionException = chainExceptions(executionException, e);
    }

    // try to backup to cloud
    try {
        cloudBackup.save();
    }
    catch (Exception e) {
        executionException = chainExceptions(executionException, e);
    }

    // thrown final exception
    if (executionException != null) {
        throw executionException;
    }
}

Every time a new exception happens it acts like a suppressor for a previous one, thus, in the end, the final stack trace will look like a tree, with the most recent exception in the root, coming down to the most former one at the bottom:

Exception java.lang.Exception: Cloud storage is not accessible
  at backup.CloudBackup.save(CloudBackup.java:7)
  at backup.BackupManager.main(BackupManager.java:31)
  Suppressed: java.lang.Exception: External Storage is not mounted
    at backup.ExternalFileSystemBackup.save(ExternalFileSystemBackup.java:7)
    at backup.BackupManager.main(BackupManager.java:23)
    Suppressed: java.lang.Exception: Local Storage is full
      at backup.LocalFileSystemBackup.save(LocalFileSystemBackup.java:7)
      at backup.BackupManager.main(BackupManager.java:15)

Full source code can be found in the GitHub repository.

Loading

Published inJava

Be First to Comment

Leave a Reply

We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept