javaexceptionexecutorservicescheduledexecutorservice

How to throw new exception in ScheduledExecutorService?


I have the following code:

public void restart() throws Exception
{
   CompletableFuture<?> delayed = new CompletableFuture<>(); 
   ScheduledExecutorService executorService = 
   Executors.newSingleThreadScheduledExecutor();
   executorService.scheduleWithFixedDelay(() -> 
   {
      try
      {
         throw new RuntimeException("My exception.");
      }
      catch(Exception e)
      {
         delayed.completeExceptionally(e);
      }
   }, 1000L, 150L, TimeUnit.MILLISECONDS));

   delayed.whenCompleteAsync((r, t) -> {
      if (t != null)
      {
         throw new RuntimeException(t);
      }
   });
}

I'm trying to bubble up the exception which I'll catch in my executorService. But what happens instead is that the exception is thrown in the try block which is caught and CompletableFuture is completedExceptionally. Which then rethrows the exception. I was hoping that this way I'd be able to bubble up the exception.

But unfortunately, that's not what's happening. The delayed throws the exception but it doesn't bubble up. On top of that for some reason, the exception loop starts right after this. Which is try keeps throwing exception and catch keeps catching but of course completableFuture has already been completed so it doesn't come to that.

The question is how can we handle and bubble up the exception?


Solution

  • This worked for me:

    public void restart() throws Exception {
        CompletableFuture<?> delayed = new CompletableFuture<>();
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleWithFixedDelay(() -> {
            try {
                throw new RuntimeException("My exception.");
            } catch(Exception e) {
                delayed.completeExceptionally(e);
            }
        }, 1000L, 150L, TimeUnit.MILLISECONDS);
    
        try {
            delayed.get();
        } catch (ExecutionException e) {
            throw (Exception) e.getCause();
        }
    }
    

    In this modified code, we are calling the get() method on the delayed CompletableFuture, which blocks until the CompletableFuture is completed. If the CompletableFuture is completed exceptionally, get() throws an ExecutionException with the original exception as its cause. We then extract the cause and rethrow it as an Exception. This way, the exception is bubbled up to the restart() method and can be handled appropriately.

    But if we want to achieve it without using blocking get call, we can use callback Consumer<Throwable> callback and if exeption throws, use callback.accept(e); like in this snippet:

        public static void main(String[] args) {
            restart((t) -> System.out.println("Exception occurred: " + t.getMessage()));
        }
    
        public static void restart(Consumer<Throwable> callback) {
            CompletableFuture.runAsync(() -> {
                try {
                    // Code that may throw an exception goes here
                    throw new RuntimeException("My exception.");
                } catch (Exception e) {
                    callback.accept(e);
                }
            });
        }