3

I'm trying to catch uncaught exceptions on futures like this CompletableFuture.runAsync(() -> {throw new RuntimeException();});

My goal is to make these exceptions not silent when developpers forget to handle them.

  • Calling get() or join() and try/catch exceptions is not an option because it is not global to all usages of future in the code base
  • Adding .exceptionnaly(...) or handle(...) is not an option for the same reason. It's exactly what I'm trying to prevent

Here's what I do (which doesn't work)

public class Main {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.setProperty("java.util.concurrent.ForkJoinPool.common.exceptionHandler", UncaughtExceptionHandler.class.getName());
        CompletableFuture.runAsync(() -> {
            System.out.println("async");
            throw new RuntimeException();
        });
        System.out.println("Done");
        Thread.sleep(1000);
    }

    static class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("Uncaught!");
        }
    }
}

It prints

Done
Async

What am I missing ?

EDIT

I tried this but still not working

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture.runAsync(() -> {
                    System.out.println("Async");
                    throw new RuntimeException();
                },
                new ForkJoinPool(
                        Runtime.getRuntime().availableProcessors(),
                        ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                        (t, e) -> System.out.println("Uncaught!"), // UncaughtExceptionHandler
                        false));
        System.out.println("Done");
        Thread.sleep(1000);
    }
}

It seems that the ForkJoinPool ignores its UncaughtExceptionHandler, and even its ForkJoinWorkerThreadFactory because I tried to define that as well

apflieger
  • 912
  • 10
  • 18
  • `CompletableFuture` objects operate on an individual thread. https://stackoverflow.com/questions/6546193/how-to-catch-an-exception-from-a-thread – Koenigsberg Oct 26 '20 at 18:18
  • I tried to set Thread.setDefaultUncaughtExceptionHandler() but doesn't work – apflieger Oct 26 '20 at 22:40
  • 1
    An uncaught exception handler does not work because the exception is not uncaught. It’s caught and recorded in the future returned by `runAsync`. Your code is ignoring that return value, but “ignored return value” is an entirely different category than “uncaught exception”. So you can’t expect the handler for the latter to be used for the former. – Holger Oct 27 '20 at 16:41

2 Answers2

0

Version 1: everything works as it should, no exception thrown by the RunAsync method ... no exception handling occurs...

public static void main(String[] args) throws InterruptedException {
        UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler();
        System.setProperty("java.util.concurrent.ForkJoinPool.common.exceptionHandler", UncaughtExceptionHandler.class.getName());
        CompletableFuture.runAsync(() -> {
            System.out.println("async");
        }).exceptionally((ex) -> {
            uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), ex); 
            return null;
        });
        System.out.println("Done");
        Thread.sleep(1000);
    }

    static class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

        public UncaughtExceptionHandler() { }
        
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("Uncaught!");
        }
    }

Output:

async
Done

Process finished with exit code 0

Version 2: Exception thrown by runAsync() and the exception handler does its thing.

 public static void main(String[] args) throws InterruptedException {
        UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler();
        System.setProperty("java.util.concurrent.ForkJoinPool.common.exceptionHandler", UncaughtExceptionHandler.class.getName());
        CompletableFuture.runAsync(() -> {
            throw new RuntimeException("Something went Wrong");
        }).exceptionally((ex) -> {
            uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), ex);
            return null;
        });
        System.out.println("Done");
        Thread.sleep(1000);
    }

    static class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

        public UncaughtExceptionHandler() { }

        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("Uncaught!");
        }
    }

Output:

Uncaught!
Done

Process finished with exit code 0

The two ways to handle exceptions:

.exceptionally(ex -> { your_code }) - gives you a chance to recover from errors generated from the original Future. You can log the exception here and return a default value. .handle((result, ex) -> { your_code }) - called whether or not an exception occurs. Can also be used to handle exceptions

Rob Evans
  • 2,822
  • 1
  • 9
  • 15
  • 1
    Yeah sure I can use these. But my goal here is specifically to catch exceptions when developpers forgot to put a .exceptionnaly or .handle. Currently they are completely silent – apflieger Oct 27 '20 at 15:08
  • hmm.. thats a lot more tricky and not something I have a solution for. I suspect some kind of static analysis **might** catch it (PMD?) or some reflective check may enable you to run your own test as part of the build? Otherwise I'm out of ideas. – Rob Evans Oct 27 '20 at 15:15
  • This looks like a good place to start investigating https://stackoverflow.com/questions/576918/how-do-i-intercept-a-method-invocation-with-standard-java-features-no-aspectj-e – Rob Evans Oct 27 '20 at 15:31
  • https://stackoverflow.com/a/576946/7619034 I used to work with Gareth! He knows what he's talking about :) – Rob Evans Oct 27 '20 at 15:49
  • 2
    Yeah, I came to the conclusion that the jdk doesn't allow to catch these exceptions. Sad. – apflieger Oct 27 '20 at 19:37
-2

You are missing the fact that a CompletableFuture executes its task in the background (using an Executor) and handles any exception thrown by its task, in order to report the task’s status from methods like isCompletedExceptionally, so there is no uncaught exception.

The exception can be propagated by calling the CompletableFuture’s get() method:

CompletableFuture<?> future =
    CompletableFuture.runAsync(() -> {
        System.out.println("async");
        throw new RuntimeException();
    });
future.get();
System.out.println("Done");

Update:

Since you don’t want to wait for the exception, you can use exceptionally or exceptionallyAsync to respond to any exception thrown by the task:

CompletableFuture<?> future =
    CompletableFuture.runAsync(() -> {
        System.out.println("async");
        throw new RuntimeException();
    });
future.exceptionally(e -> {
    System.out.println("Uncaught!");
    return null;
});
System.out.println("Done");
VGR
  • 40,506
  • 4
  • 48
  • 63