Is there a way to make Runnable's run() throw an exception?

Viewed 93214

A method I am calling in run() in a class that implements Runnable) is designed to be throwing an exception.

But the Java compiler won't let me do that and suggests that I surround it with try/catch.

The problem is that by surrounding it with a try/catch I make that particular run() useless. I do want to throw that exception.

If I specify throws for run() itself, the compiler complains that Exception is not compatible with throws clause in Runnable.run().

Ordinarily I'm totally fine with not letting run() throw an exception. But I have unique situation in which I must have that functionality.

How to I work around this limitation?

9 Answers
@FunctionalInterface
public interface CheckedRunnable<E extends Exception> extends Runnable {

    @Override
    default void run() throws RuntimeException {
        try {
            runThrows();
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    void runThrows() throws E;

}

Some people try to convince you that you have to play by the rules. Listen, but whether you obey, you should decide yourself depending on your situation. The reality is "you SHOULD play by the rules" (not "you MUST play by the rules"). Just be aware that if you do not play by the rules, there might be consequences.

The situation not only applies in the situation of Runnable, but with Java 8 also very frequently in the context of Streams and other places where functional interfaces have been introduced without the possibility to deal with checked exceptions. For example, Consumer, Supplier, Function, BiFunction and so on have all been declared without facilities to deal with checked exceptions.

So what are the situations and options? In the below text, Runnable is representative of any functional interface that doesn't declare exceptions, or declares exceptions too limited for the use case at hand.

  1. You've declared Runnable somewhere yourself, and could replace Runnable with something else.
    1. Consider replacing Runnable with Callable<Void>. Basically the same thing, but allowed to throw exceptions; and has to return null in the end, which is a mild annoyance.
    2. Consider replacing Runnable with your own custom @FunctionalInterface that can throw exactly those exceptions that you want.
  2. You've used an API, and alternatives are available. For example, some Java APIs are overloaded so you could use Callable<Void> instead of Runnable.
  3. You've used an API, and there are no alternatives. In that case, you're still not out of options.
    1. You can wrap the exception in RuntimeException.
    2. You can hack the exception into a RuntimeException by using an unchecked cast.

You can try the following. It's a bit of a hack, but sometimes a hack is what we need. Because, whether an exception should be checked or unchecked is defined by its type, but practically should actually be defined by the situation.

@FunctionalInterface
public interface ThrowingRunnable extends Runnable {
    @Override
    default void run() {
        try {
            tryRun();
        } catch (final Throwable t) {
            throwUnchecked(t);
        }
    }

    private static <E extends RuntimeException> void throwUnchecked(Throwable t) {
        throw (E) t;
    }

    void tryRun() throws Throwable;
}

I prefer this over new RuntimeException(t) because it has a shorter stack trace.

You can now do:

executorService.submit((ThrowingRunnable) () -> {throw new Exception()});

Disclaimer: The ability to perform unchecked casts in this way might actually be removed in future versions of Java, when generics type information is processed not only at compile time, but also at runtime.

Related