ScheduledExecutorService Exception handling
You should use the ScheduledFuture
object returned by your scheduler.scheduleWithFixedDelay(...)
like so :
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
scheduler.scheduleWithFixedDelay(new Runnable() {
public void run() {
throw new RuntimeException("foo");
}
}, 1, 10, TimeUnit.SECONDS);
// Create and Start an exception handler thread
// pass the "handle" object to the thread
// Inside the handler thread do :
....
try {
handle.get();
} catch (ExecutionException e) {
Exception rootException = e.getCause();
}
tl;dr
Any exception escaping your run
method halts all further work, without notice.
Always use a try-catch
within your run
method. Try to recover if you want scheduled activity to continue.
@Override
public void run ()
{
try {
doChore();
} catch ( Exception e ) {
logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
}
}
The Problem
The question refers to the critical trick with a ScheduledExecutorService
: Any thrown exception or error reaching the executor causes the executor to halt. No more invocations on the Runnable, no more work done. This work stoppage happens silently, you'll not be informed. This naughty-language blog posting entertainingly narrates the hard way to learn about this behavior.
The Solution
The answer by yegor256 and the answer by arun_suresh both seem to be basically correct. Two issues with those answers:
- Catch errors as well as exceptions
- A bit complicated
Errors and Exceptions ?
In Java we normally catch only exceptions, not errors. But in this special case of ScheduledExecutorService, failing to catch either will mean a work stoppage. So you may want to catch both. I'm not 100% sure about this, not knowing fully the implications of catching all errors. Please correct me if needed.
One reason to catch errors as well as exceptions might involve the use of libraries within your task. See the comment by jannis.
One way to catch both exceptions and errors is to catch their superclass, Throwable for an example.
} catch ( Throwable t ) {
…rather than…
} catch ( Exception e ) {
Simplest Approach: Just Add a Try-Catch
But both answers are a bit complicated. Just for the record, I'll show the simplest solution:
Always wrap your Runnable's code in a Try-Catch to catch any and all exceptions and errors.
Lambda Syntax
With a lambda (in Java 8 and later).
final Runnable someChoreRunnable = () -> {
try {
doChore();
} catch ( Throwable t ) { // Catch Throwable rather than Exception (a subclass).
logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
}
};
Old-Fashioned Syntax
The old-fashioned way, before lambdas.
final Runnable someChoreRunnable = new Runnable()
{
@Override
public void run ()
{
try {
doChore();
} catch ( Throwable t ) { // Catch Throwable rather than Exception (a subclass).
logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
}
}
};
In Every Runnable/Callable
Regardless of a ScheduledExecutorService
, it seems sensible to me to always use a general try-catch( Exception† e )
in any run
method of a Runnable
. Ditto for any call
method of a Callable
.
Complete example code
In real work, I would likely define the Runnable
separately rather than nested. But this makes for neat all-in-one example.
package com.basilbourque.example;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Demo `ScheduledExecutorService`
*/
public class App {
public static void main ( String[] args ) {
App app = new App();
app.doIt();
}
private void doIt () {
// Demonstrate a working scheduled executor service.
// Run, and watch the console for 20 seconds.
System.out.println( "BASIL - Start." );
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture < ? > handle =
scheduler.scheduleWithFixedDelay( new Runnable() {
public void run () {
try {
// doChore ; // Do business logic.
System.out.println( "Now: " + ZonedDateTime.now( ZoneId.systemDefault() ) ); // Report current moment.
} catch ( Exception e ) {
// … handle exception/error. Trap any unexpected exception here rather to stop it reaching and shutting-down the scheduled executor service.
// logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + e.getStackTrace() );
} // End of try-catch.
} // End of `run` method.
} , 0 , 2 , TimeUnit.SECONDS );
// Wait a long moment, for background thread to do some work.
try {
Thread.sleep( TimeUnit.SECONDS.toMillis( 20 ) );
} catch ( InterruptedException e ) {
e.printStackTrace();
}
// Time is up. Kill the executor service and its thread pool.
scheduler.shutdown();
System.out.println( "BASIL - Done." );
}
}
When run.
BASIL - Start.
Now: 2018-04-10T16:46:01.423286-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:03.449178-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:05.450107-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:07.450586-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:09.456076-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:11.456872-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:13.461944-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:15.463837-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:17.469218-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:19.473935-07:00[America/Los_Angeles]
BASIL - Done.
Another example
Here is another example. Here our task is meant to run about twenty times, once every five seconds for a minute. But on the fifth run, we throw an exception.
public class App2
{
public static void main ( String[] args )
{
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
final AtomicInteger counter = new AtomicInteger( 0 );
Runnable task = ( ) -> {
int c = counter.incrementAndGet();
if ( c > 4 )
{
System.out.println( "THROWING EXCEPTION at " + Instant.now() );
throw new IllegalStateException( "Bogus exception. c = " + c + ". " + Instant.now() ); // Notice how this exception is silently swallowed by the scheduled executor service, while causing a work stoppage.
}
System.out.println( "Task running. c = " + c + ". " + Instant.now() );
};
ses.scheduleAtFixedRate( task , 0 , 5 , TimeUnit.SECONDS );
try { Thread.sleep( Duration.ofMinutes( 1 ).toMillis() ); }catch ( InterruptedException e ) { e.printStackTrace(); }
System.out.println( "Main thread done sleeping. " + Instant.now() );
ses.shutdown();
try { ses.awaitTermination( 1 , TimeUnit.MINUTES ); }catch ( InterruptedException e ) { e.printStackTrace(); }
}
}
When run.
Task running. c = 1. 2021-10-14T20:09:16.317995Z
Task running. c = 2. 2021-10-14T20:09:21.321536Z
Task running. c = 3. 2021-10-14T20:09:26.318642Z
Task running. c = 4. 2021-10-14T20:09:31.318320Z
THROWING EXCEPTION at 2021-10-14T20:09:36.321458Z
Main thread done sleeping. 2021-10-14T20:10:16.320430Z
Notice:
- The exception is silently swallowed by the scheduled executor service.
- A work stoppage occurs. No further executions of our task are scheduled. Again, a silent problem.
So when your task throws an exception, you get the worst outcome possible: Silent work stoppage with no explanation.
The solution, as mentioned above: Always use a try-catch
within your run
method.
† Or perhaps Throwable
instead of Exception
to catch Error
objects too.
Old question but the accepted answer doesn't give explanations and provides a poor example and the most upvoted answer is right on some points but finally encourages you to add catch
exceptions in every Runnable.run()
method.
I disagree because :
- it is not neat : not standard for a task to catch its own exceptions.
- it is not robust : a new Runnable subclass could forget to perform the exception catch and the failover associated.
- it defeats the low coupling promoted by tasks since that couples the tasks to execute with the way of handling the task result.
- it mixes responsibilities : that is not the task responsibility to handle the exception or to communicate the exception to the caller. A task is something to execute.
I think that the exception propagation should be performed by the ExecutorService
framework and actually it offers that feature.
Besides, trying to be too clever by trying to short-circuiting the ExecutorService
way of working is not a good idea either : the framework may evolve and you want to use it in a standard way.
At last, letting the ExecutorService
framework to make its job doesn't mean necessarily halting the subsequent invocations task.
If a scheduled task encounters an issue, that is the caller responsibility to re-schedule or not the task according to the issue cause.
Each layer has its its responsibilities. Keeping these make code both clear and maintainable.
ScheduledFuture.get() : the right API to catch exceptions and errors occurred in the task
ScheduledExecutorService.scheduleWithFixedDelay()/scheduleAtFixRate()
state in their specification :
If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor.
It means that ScheduledFuture.get()
doesn't return at each scheduled invocation but that it returns for the last invocation of the task, that is a task cancelation : caused by ScheduledFuture.cancel()
or a exception thrown in the task.
So handling the ScheduledFuture
return to capture the exception with ScheduledFuture.get()
looks right :
try {
future.get();
} catch (InterruptedException e) {
// ... to handle
} catch (ExecutionException e) {
// ... and unwrap the exception OR the error that caused the issue
Throwable cause = e.getCause();
}
Example with the default behavior : halting the scheduling if one of the task execution encounters an issue
It executes a task that for the third executions thrown an exception and terminates the scheduling. In some scenarios, we want that.
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ScheduledExecutorServiceWithException {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// variable used to thrown an error at the 3rd task invocation
AtomicInteger countBeforeError = new AtomicInteger(3);
// boolean allowing to leave the client to halt the scheduling task or not after a failure
Future<?> futureA = executor
.scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
try {
System.out.println("before get()");
futureA.get(); // will return only if canceled
System.out.println("after get()");
} catch (InterruptedException e) {
// handle that : halt or no
} catch (ExecutionException e) {
System.out.println("exception caught :" + e.getCause());
}
// shutdown the executorservice
executor.shutdown();
}
private static class MyRunnable implements Runnable {
private final AtomicInteger invocationDone;
public MyRunnable(AtomicInteger invocationDone) {
this.invocationDone = invocationDone;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ", execution");
if (invocationDone.decrementAndGet() == 0) {
throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
}
}
}
}
Output :
before get() pool-1-thread-1, execution pool-1-thread-1, execution pool-1-thread-1, execution exception caught :java.lang.IllegalArgumentException: ohhh an Exception in MyRunnable
Example with the possibility to go on the scheduling if one of the task execution encounters an issue
It executes a task that throws an exception at the two first executions and throws an error at the third one. We can see that the client of the tasks can choose to halt or not the scheduling : here I go on in cases of exception and I stop in case of error.
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ScheduledExecutorServiceWithException {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// variable used to thrown an error at the 3rd task invocation
AtomicInteger countBeforeError = new AtomicInteger(3);
// boolean allowing to leave the client to halt the scheduling task or not after a failure
boolean mustHalt = true;
do {
Future<?> futureA = executor
.scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
try {
futureA.get(); // will return only if canceled
} catch (InterruptedException e) {
// handle that : halt or not halt
} catch (ExecutionException e) {
if (e.getCause() instanceof Error) {
System.out.println("I halt in case of Error");
mustHalt = true;
} else {
System.out.println("I reschedule in case of Exception");
mustHalt = false;
}
}
}
while (!mustHalt);
// shutdown the executorservice
executor.shutdown();
}
private static class MyRunnable implements Runnable {
private final AtomicInteger invocationDone;
public MyRunnable(AtomicInteger invocationDone) {
this.invocationDone = invocationDone;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ", execution");
if (invocationDone.decrementAndGet() == 0) {
throw new Error("ohhh an Error in MyRunnable");
} else {
throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
}
}
}
}
Output :
pool-1-thread-1, execution I reschedule in case of Exception pool-1-thread-1, execution I reschedule in case of Exception pool-1-thread-2, execution I halt in case of Error