Running on Java 26-ea+20-2057 (Preview)
Home of The JavaSpecialists' Newsletter

329Filtering Exceptions via try

Author: Dr Heinz M. KabutzDate: 2025-10-30Java Version: 25Sources on GitHubCategory: Language
 

Abstract: One of the ways of figuring out the type of an exception is with instanceof. Another is to re-throw it, and to use the catch mechanism to sort things out. In this newsletter we look at different options, including switch with pattern matching.

 

A hearty welcome to our 329th edition of The Java(tm) Specialists' Newsletter. In 2020, when we were in full lockdown re COVID, our son Maximilian persuaded me that it would be such fun to own a pony. We could take the girls for a ride around our village, and use the beasts of burden to carry our heavy sacks of chicken feed to our shed. And so, in an act of madness, "Thunder" joined our household. He was a strange fellow from the beginning, and somehow developed a fear of walking on paving. After much tugging and pulling, and almost giving up, Maxi suggested that we take our green olive harvesting nets and drape them over our driveway. Thunder, after more coaxing, hopped onto the nets, imagining them to be lushous grass. Eventually, after more encouragement, he hopped over onto the other side, and could enjoy the delicious greens that grow at the bottom of our garden. Nowadays, I try to take him for a walk in our neighbourhood every evening, and it is quite a sight, with a very round pony and an equally round owner, wandering around like I'm taking Rex for a walk. He loves eating carob pods, so whenever we get to a tree, he has to sniff and eat his fill, which can take quite a while. Eating like a horse means, you never stop eating. It's a slow walk, but fun for both of us. And of course our tough youngest Efi gets to ride him whenever she has time.

Next month is our annual Black Friday sale, and as always, we offer a generous discount on our flagship Java Specialists Superpack. If you have training budget left for 2025, rather wait until the 28th of November to get our Superpack :-) Don't buy yet - wait for Black Friday.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Filtering Exceptions via try

I received a question today about some "interesting" code I had written to filter exceptions. For example, when we catch an InvocationTargetException, all we know is that the cause is of type Throwable. We do not know whether the cause was checked or unchecked. Back in the day, before we had pattern matching, I used a coding pattern that looked like this:

try {
  throw cause;
} catch (Error | RuntimeException unchecked) {
  throw unchecked;
} catch (Throwable checked) {
  throw new IllegalStateException(checked);
}

Another way would have been to figure out what it was using instanceof and casts or pattern matching.

I have always liked my coding idiom, and would love to hear what you think? Nice, or should we rather use switch with pattern matching or instanceof?

Let's look at a longer example, we start with a really GrumpyCode, maybe woke up from their yapping dog after a too short siesta ...

import module java.base;
import module java.sql;

public final class GrumpyCode {
    public void brokenTable() throws SQLException {
        throw new SQLException("Can't stand JDBC");
    }

    public void talkToWife() {
        throw new IllegalArgumentException(
                "Who left the toilet seat up?");
    }

    public void whereAreMyGlasses() {
        throw new OutOfMemoryError("On my head!");
    }

    public void talkToSelf() throws IOException {
        throw new IOException("Connection closed");
    }
}

I can feel the tension in that class. Deep breathing - in, out, in out. And relax.

We now want to explore different approaches we could use to sanitize a InvocationTargetException cause, so we have our InvocationTargetSantizier:

import module java.base;

public sealed interface InvocationTargetSantizier
        permits InstanceOfCastSanitizer,
        InstanceOfPatternMatchingSanitizer,
        SwitchPatternMatchingSanitizer, TryThrowSanitizer {
    void sanitize(InvocationTargetException e);
}

The first sanitizer is how we would perhaps have written the code in Java 7, before we had pattern matching:

import module java.base;

public final class InstanceOfCastSanitizer
        implements InvocationTargetSantizier {
    public void sanitize(InvocationTargetException e) {
        var cause = e.getCause();
        if (cause instanceof Error)
            throw (Error) cause;
        if (cause instanceof RuntimeException)
            throw (RuntimeException) cause;
        if (cause instanceof IOException)
            throw npublic ew UncheckedIOException(
                    (IOException) cause);
        throw new IllegalStateException(cause);
    }
}

Heinz's baked algorithm would look like this:

import module java.base;

public final class TryThrowSanitizer
        implements InvocationTargetSantizier {
    public void sanitize(InvocationTargetException e) {
        try {
            throw e.getCause();
        } catch (RuntimeException | Error unchecked) {
            throw unchecked;
        } catch (IOException ioe) {
            throw new UncheckedIOException(ioe);
        } catch (Throwable t) {
            throw new IllegalStateException(t);
        }
    }
}

But with Java 16, we got pattern matching for instanceof, making the first approach a bit more readable, like so:

import module java.base;

public final class InstanceOfPatternMatchingSanitizer
        implements InvocationTargetSantizier {
    public void sanitize(InvocationTargetException e) {
        var cause = e.getCause();
        if (cause instanceof Error error)
            throw error;
        if (cause instanceof RuntimeException re)
            throw re;
        if (cause instanceof IOException ioe)
            throw new UncheckedIOException(ioe);
        throw new IllegalStateException(cause);
    }
}

And to be honest, I still prefer my try-catch approach, especially because we can cover both Error and RuntimeException in one catch. This would not work with pattern matching.

In Java 21, we got the nice switch with pattern matching, so we could use that instead:

import module java.base;

public final class SwitchPatternMatchingSanitizer
        implements InvocationTargetSantizier {
    public void sanitize(InvocationTargetException e) {
        switch (e.getCause()) {
            case Error error -> throw error;
            case RuntimeException re -> throw re;
            case IOException ioe ->
                    throw new UncheckedIOException(ioe);
            case Throwable t ->
                    throw new IllegalStateException(t);
        }
    }
}

It would be nice if we could also throw a switch expression, but the compiler does not seem to be able to determine that all the exceptions that are being thrown are unchecked. It simply sees Throwable. Should this have been supported? I don't know - what do you think? It could be that the language designers didn't think of this use case?

import module java.base;

public final class ThrowUsingSwitchExpressionSanitizer {
    public void sanitize(InvocationTargetException e) {
        // Does not compile:
        //   Unhandled exception: java.lang.Throwable
        throw switch (e.getCause()) {
            case Error error -> error;
            case RuntimeException re -> re;
            case IOException ioe ->
                    new UncheckedIOException(ioe);
            case Throwable throwable ->
                    new IllegalStateException(throwable);
        };
    }
}

Let's put this all together in our GrumpyCodeCaller. You will now see why we used sealed classes, so that we can find all the permitted subclasses.

import module java.base;

public class GrumpyCodeCaller {
    public void callMethodsByRandom(
            InvocationTargetSantizier sanitizer)
            throws IllegalAccessException {
        System.out.println("Using sanitizer " +
                           sanitizer.getClass());
        var grumpy = new GrumpyCode();
        var methods = GrumpyCode.class.getDeclaredMethods();
        for (int i = 0; i < 10; i++) {
            var method = methods[
                    ThreadLocalRandom.current()
                            .nextInt(methods.length)];
            try {
                method.invoke(grumpy);
            } catch (InvocationTargetException e) {
                try {
                    sanitizer.sanitize(e);
                } catch (Error | RuntimeException ex) {
                    System.out.println(ex);
                }
            }
        }
        System.out.println();
    }

    void main() throws ReflectiveOperationException {
        for (Class<?> sanitizerClass :
                InvocationTargetSantizier.class
                        .getPermittedSubclasses()) {
            var subclass = sanitizerClass.asSubclass(
                    InvocationTargetSantizier.class);
            var sanitizer = subclass
                    .getConstructor()
                    .newInstance();
            callMethodsByRandom(sanitizer);
        }
    }
}

All our sanitizers do the same basic job, so the output should not be surprising:

openjdk version "25.0.1" 2025-10-21
OpenJDK Runtime Environment (build 25.0.1+8-27)
OpenJDK 64-Bit Server VM (build 25.0.1+8-27, mixed mode, sharing)
Using sanitizer class InstanceOfCastSanitizer
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.OutOfMemoryError: On my head!
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC

Using sanitizer class InstanceOfPatternMatchingSanitizer
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.lang.OutOfMemoryError: On my head!
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC

Using sanitizer class SwitchPatternMatchingSanitizer
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.lang.OutOfMemoryError: On my head!
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.lang.OutOfMemoryError: On my head!
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC

Using sanitizer class TryThrowSanitizer
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalArgumentException: Who left the toilet seat up?
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.lang.OutOfMemoryError: On my head!
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.io.UncheckedIOException: java.io.IOException: Connection closed
java.lang.IllegalStateException: java.sql.SQLException: Can't stand JDBC

Which is your favourite approach? Vote either on Twitter/X of LinkedIn.

Kind regards

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack

Java Specialists Superpack Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...