The Spring TaskScheduler Abstraction

In addition to the TaskExecutor abstraction, Spring 3.0 introduced a TaskScheduler with a variety of methods for scheduling tasks to run at some point in the future. The following listing shows the TaskScheduler interface definition:

public interface TaskScheduler {

	ScheduledFuture schedule(Runnable task, Trigger trigger);

	ScheduledFuture schedule(Runnable task, Instant startTime);

	ScheduledFuture schedule(Runnable task, Date startTime);

	ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);

	ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

	ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);

	ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

	ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);

	ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

	ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);

	ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

The simplest method is the one named schedule that takes only a Runnable and a Date. That causes the task to run once after the specified time. All of the other methods are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay methods are for simple, periodic execution, but the method that accepts a Trigger is much more flexible.

Trigger Interface

The Trigger interface is essentially inspired by JSR-236 which, as of Spring 3.0, was not yet officially implemented. The basic idea of the Trigger is that execution times may be determined based on past execution outcomes or even arbitrary conditions. If these determinations do take into account the outcome of the preceding execution, that information is available within a TriggerContext. The Trigger interface itself is quite simple, as the following listing shows:

public interface Trigger {

	Date nextExecutionTime(TriggerContext triggerContext);
}

The TriggerContext is the most important part. It encapsulates all of the relevant data and is open for extension in the future, if necessary. The TriggerContext is an interface (a SimpleTriggerContext implementation is used by default). The following listing shows the available methods for Trigger implementations.

public interface TriggerContext {

	Date lastScheduledExecutionTime();

	Date lastActualExecutionTime();

	Date lastCompletionTime();
}

Trigger Implementations

Spring provides two implementations of the Trigger interface. The most interesting one is the CronTrigger. It enables the scheduling of tasks based on cron expressions. For example, the following task is scheduled to run 15 minutes past each hour but only during the 9-to-5 “business hours” on weekdays:

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

The other implementation is a PeriodicTrigger that accepts a fixed period, an optional initial delay value, and a boolean to indicate whether the period should be interpreted as a fixed-rate or a fixed-delay. Since the TaskScheduler interface already defines methods for scheduling tasks at a fixed rate or with a fixed delay, those methods should be used directly whenever possible. The value of the PeriodicTrigger implementation is that you can use it within components that rely on the Trigger abstraction. For example, it may be convenient to allow periodic triggers, cron-based triggers, and even custom trigger implementations to be used interchangeably. Such a component could take advantage of dependency injection so that you can configure such Triggers externally and, therefore, easily modify or extend them.

TaskScheduler implementations

As with Spring’s TaskExecutor abstraction, the primary benefit of the TaskScheduler arrangement is that an application’s scheduling needs are decoupled from the deployment environment. This abstraction level is particularly relevant when deploying to an application server environment where threads should not be created directly by the application itself. For such scenarios, Spring provides a TimerManagerTaskScheduler that delegates to a CommonJ TimerManager on WebLogic or WebSphere as well as a more recent DefaultManagedTaskScheduler that delegates to a JSR-236 ManagedScheduledExecutorService in a Java EE 7+ environment. Both are typically configured with a JNDI lookup.

Whenever external thread management is not a requirement, a simpler alternative is a local ScheduledExecutorService setup within the application, which can be adapted through Spring’s ConcurrentTaskScheduler. As a convenience, Spring also provides a ThreadPoolTaskScheduler, which internally delegates to a ScheduledExecutorService to provide common bean-style configuration along the lines of ThreadPoolTaskExecutor. These variants work perfectly fine for locally embedded thread pool setups in lenient application server environments, as well — in particular on Tomcat and Jetty.