Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

In this quick tutorial, we're going to learn about suppressed exceptions in Java. In short, a suppressed exception is an exception that is thrown but somehow ignored. A common scenario for this in Java is when the finally block throws an exception. Any exception originally thrown in the try block is then suppressed.

Starting with Java 7, we can now use two methods on the Throwable class to handle our suppressed exceptions: addSuppressed and getSuppressed. We should note that the try-with-resources construct was also introduced in Java 7. We'll see in our examples how they're related.

2. Suppressed Exceptions in Action

2.1. Suppressed Exception Scenario

Let's begin by taking a quick look at an example where the original exception is suppressed by an exception occurring in the finally block:

public static void demoSuppressedException(String filePath) throws IOException {
    FileInputStream fileIn = null;
    try {
        fileIn = new FileInputStream(filePath);
    } catch (FileNotFoundException e) {
        throw new IOException(e);
    } finally {
        fileIn.close();
    }
}

As long as we provide a path to an existing file, no exceptions will be thrown and the method will work as expected.

However, suppose we provide a file that doesn't exist:

@Test(expected = NullPointerException.class)
public void givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException() throws IOException {
    demoSuppressedException("/non-existent-path/non-existent-file.txt");
}

In this case, the try block will throw a FileNotFoundException when it tries to open the non-existent file. Because the fileIn object was never initialized, it'll throw a NullPointerException when we try to close it in our finally block. Our calling method will only get the NullPointerException, and it won't be readily obvious what the original problem was: that the file doesn't exist.

2.2. Adding Suppressed Exception

Now let's look at how we can take advantage of the Throwable.addSuppressed method to provide the original exception:

public static void demoAddSuppressedException(String filePath) throws IOException {
    Throwable firstException = null;
    FileInputStream fileIn = null;
    try {
        fileIn = new FileInputStream(filePath);
    } catch (IOException e) {
        firstException = e;
    } finally {
        try {
            fileIn.close();
        } catch (NullPointerException npe) {
            if (firstException != null) {
                npe.addSuppressed(firstException);
            }
            throw npe;
        }
    }
}

Let's go to our unit test and see how getSuppressed works in this situation:

try {
    demoAddSuppressedException("/non-existent-path/non-existent-file.txt");
} catch (Exception e) {
    assertThat(e, instanceOf(NullPointerException.class));
    assertEquals(1, e.getSuppressed().length);
    assertThat(e.getSuppressed()[0], instanceOf(FileNotFoundException.class));
}

We now have access to that original exception from the array of suppressed exceptions provided.

2.3. Using try-with-resources

Lastly, let's look at an example using try-with-resources where the close method throws an exception. Java 7 introduced the try-with-resources construct and the AutoCloseable interface for resource management.

First, let's create a resource that implements AutoCloseable:

public class ExceptionalResource implements AutoCloseable {
    
    public void processSomething() {
        throw new IllegalArgumentException("Thrown from processSomething()");
    }

    @Override
    public void close() throws Exception {
        throw new NullPointerException("Thrown from close()");
    }
}

Next, let's use our ExceptionalResource in a try-with-resources block:

public static void demoExceptionalResource() throws Exception {
    try (ExceptionalResource exceptionalResource = new ExceptionalResource()) {
        exceptionalResource.processSomething();
    }
}

Finally, let's go over to our unit test and see how the exceptions shake out:

try {
    demoExceptionalResource();
} catch (Exception e) {
    assertThat(e, instanceOf(IllegalArgumentException.class));
    assertEquals("Thrown from processSomething()", e.getMessage());
    assertEquals(1, e.getSuppressed().length);
    assertThat(e.getSuppressed()[0], instanceOf(NullPointerException.class));
    assertEquals("Thrown from close()", e.getSuppressed()[0].getMessage());
}

We should note that when using AutoCloseable, it's the exception thrown in the close method that's suppressed. The original exception is thrown.

3. Conclusion

In this short tutorial, we learned what suppressed exceptions are and how they happen. Then, we saw how to use the addSuppressed and getSuppressed methods to access those suppressed exceptions. Finally, we saw how suppressed exceptions work when using a try-with-resources block.

As always, the example code is available over on GitHub.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!