The Spring framework provides abstraction for asynchronous execution and scheduling of tasks through the TaskExecutor and TaskScheduler interfaces respectively. Spring also provides implementations of those interfaces that support thread pools or CommonJ delegates in application server environments. Ultimately, using these implementations behind a common interface eliminates the differences between JavaSE5, JavaSE6, and JakartaEE environments.
Spring also has integration classes to support scheduling with Timer (part of JDK since 1.3) and Quartz Scheduler. You set up these two schedulers using a FactoryBean and an optional Timer or Trigger instance reference, respectively. Additionally, both Quartz Scheduler and Timer have a convenience class that allows you to invoke methods on existing target objects (similar to normal MethodInvokingFactoryBean operations).
1. The concept of Spring TaskExecutor
Executor is the JDK name for the thread pool concept. The "executor" is named because there is no guarantee that the underlying implementation is actually a pool. Executors can be single-threaded or even synchronous. Spring's abstraction hides the implementation details between the JavaSE and JakartaEE environments.
Spring's TaskExecutor interface is identical to the java.util.concurrent.Executor interface. In fact, originally, the main reason it existed was to not require Java5 when using thread pools. This interface has a single method (execute(Runnable task)) which accepts a task to execute according to the semantics and configuration of the thread pool.
TaskExecutor was originally created to provide thread pool abstraction to other Spring components when needed. Components such as ApplicationEventMulticaster, JMS's AbstractMessageListenerContainer, and Quartz integration all use the TaskExecutor abstraction to pool threads. However, if your bean s require thread pool behavior, you can also use this abstraction according to your needs.
1.1 TaskExecutor type
Spring includes a number of pre-built TaskExecutor implementations. Chances are, you'll never need to implement your own. The variants provided by Spring are as follows:
SyncTaskExecutor: This implementation does not run calls asynchronously. Instead, each call occurs in the calling thread. It is mainly used when multi-threading is not required, such as in simple test cases.
SimpleAsyncTaskExecutor: This implementation does not reuse any threads. Instead, it starts a new thread for each call. It does, however, support a concurrency limit that blocks any calls that exceed that limit until the slot is freed. If you're looking for a real pool, see ThreadPoolTaskExecutor later in this list.
ConcurrentSkExecutor: This implementation is an adapter for java.util.concurrent.Executor instances. There is also an alternative (ThreadPoolTaskExecutor) that exposes Executor configuration parameters as bean properties. It is rarely necessary to use ConcurrentTaskExecutor directly. However, if ThreadPoolTaskExecutor is not flexible enough for your needs, ConcurrentTaskExecutor is another option.
ThreadPoolTaskExecutor: This implementation is most commonly used. It exposes the bean property used to configure the java.util.concurrent.ThreadPoolExecutor and wraps it in a TaskExecutor. If you need to accommodate a different type of java.util.concurrent.Executor, we recommend that you use ConcurrentSkExecutor instead.
DefaultManagedTaskExecutor: This implementation uses a JNDI-obtained ManagedExecutorService in a JSR-236 compliant runtime environment such as the Jakarta EE application server, replacing the CommonJ WorkManager.
1.2 TaskExecutor use
Spring's TaskExecutor implementation is used as simple JavaBeans. In the following example, we define a bean that uses a ThreadPoolTaskExecutor to asynchronously print a set of messages:
import org.springframework.core.task.TaskExecutor; public class TaskExecutorExample { private class MessagePrinterTask implements Runnable { private String message; public MessagePrinterTask(String message) { this.message = message; } public void run() { System.out.println(message); } } private TaskExecutor taskExecutor; public TaskExecutorExample(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } public void printMessages() { for(int i = 0; i < 25; i++) { taskExecutor.execute(new MessagePrinterTask("Message" + i)); } } }
As you can see, instead of retrieving the thread from the pool and executing it yourself, you add the Runnable to the queue. The TaskExecutor then uses its internal rules to decide when the task should run.
To configure the rules used by the TaskExecutor we expose simple bean properties:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="5"/> <property name="maxPoolSize" value="10"/> <property name="queueCapacity" value="25"/> </bean> <bean id="taskExecutorExample" class="TaskExecutorExample"> <constructor-arg ref="taskExecutor"/> </bean>
2. Overview of Spring TaskScheduler
In addition to the TaskExecutor abstraction, Spring also has a TaskScheduler SPI that has various methods for scheduling tasks to run at some point in the future. The following listing shows the TaskScheduler interface definition:
public interface TaskScheduler { Clock getClock(); ScheduledFuture schedule(Runnable task, Trigger trigger); ScheduledFuture schedule(Runnable task, Instant startTime); ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period); ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period); ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay); ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
The easiest way is a method called schedule which just takes a Runnable and an Instant. This causes the task to run once after the specified time. All other methods are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay methods are used for simple periodic execution, but the methods that accept triggers are much more flexible.
1.Trigger interface
The Trigger interface is essentially inspired by JSR-236. The basic idea of a trigger is that execution time can be determined based on past execution results or even arbitrary conditions. If these determinations take into account the results of previous executions, that information is available in the TriggerContext. The Trigger interface itself is very simple, as shown in the following table:
public interface Trigger { Instant nextExecution(TriggerContext triggerContext); }
TriggerContext is the most important part. It encapsulates all relevant data and can be extended in the future if required. TriggerContext is an interface (implemented with SimpleTriggerContext by default). The list below shows the available methods for Trigger implementations.
public interface TriggerContext { Clock getClock(); Instant lastScheduledExecution(); Instant lastActualExecution(); Instant lastCompletion(); }
2.2. Implementation of the Trigger interface
Spring provides two implementations of the Trigger interface. The most interesting one is CronTrigger. It supports task scheduling based on cron expressions. For example, the following task is scheduled to run 15 minutes after every hour, but only during the 9-5 "working hours" on weekdays:
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
Another implementation is PeriodicTrigger, which accepts a fixed period, an optional initial delay value, and a boolean value 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, these methods should be used as directly as possible. The value of the PeriodicTrigger implementation is that you can use it in components that depend on the Trigger abstraction. For example, it may be convenient to allow periodic triggers, cron-based triggers, or even custom trigger implementations to be used interchangeably. Such components can take advantage of dependency injection so that you can configure such triggers externally, modifying or extending them easily.
2.3. Implementation of TaskScheduler
Like Spring's TaskExecutor abstraction, the main benefit of TaskScheduler scheduling is the separation of an application's scheduling needs from the deployment environment. This level of abstraction is especially important when deploying to an application server environment, since the application itself should not create threads directly. For such scenarios, Spring provides a TimerManagerTaskScheduler that delegates to the CommonJ TimerManager on WebLogic or WebSphere, and a newer DefaultManagedTaskScheduler that delegates to the JSR-236 ManagedScheduledExecutorService in a Jakarta EE environment. Both are usually configured with JNDI lookups.
Whenever external thread management is not required, a simpler alternative is to set up a local ScheduledExecutorService in the application, which can be tuned via Spring's ConcurrentTaskScheduler. For convenience, Spring also provides ThreadPoolTaskScheduler , which internally delegates to ScheduledExecutorService to provide a generic bean-style configuration similar to ThreadPoolTaskExecutor . These variants also work well for local embedded thread pool setups in relaxed application server environments—especially on Tomcat and Jetty.
3. Annotation support for scheduling and asynchronous execution
Spring provides annotation support for task scheduling and asynchronous method execution.
3.1. Enable scheduling annotations
To enable support for @Scheduled and @Async annotations, you can add @EnableScheduling and @EnableAsync to one of your @Configuration classes, as shown in the following example:
@Configuration @EnableAsync @EnableScheduling public class AppConfig { }
You can select relevant notes for your application. For example, @EnableAsync can be omitted if only support for @Scheduled is required. For finer-grained control, the SchedulingConfigurer interface, AsyncConfigurer interface, or both can additionally be implemented. See the SchedulingConfigurer and AsyncConfigurer javadoc s for details.
If you prefer XML configuration, you can use the <task:annotation-driven> element, as shown in the following example:
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/> <task:executor id="myExecutor" pool-size="5"/> <task:scheduler id="myScheduler" pool-size="10"/>
Note that for the preceding XML, an executor reference is provided to handle tasks corresponding to methods annotated with @Async, while a scheduler reference is provided to manage methods annotated with @Scheduled.
3.2. @Scheduled annotation
You can add @Scheduled annotation to methods along with trigger metadata. For example, the following method is called every five seconds (5000 milliseconds) with a fixed delay, meaning that the time period is counted from the time each previous call completed.
By default, milliseconds will be used as the unit of time for fixed delay, fixed rate, and initial delay values. If you want to use a different time unit, such as seconds or minutes, you can configure it through the timeUnit attribute in @Scheduled.@Scheduled(fixedDelay = 5000) public void doSomething() { // something that should run periodically }
For example, the previous example could also be written as follows.
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) public void doSomething() { // something that should run periodically }
If you need fixed rate execution, you can use the fixedRate attribute in the annotation. The following method is called every five seconds (measured between successive start times of each call).
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) public void doSomething() { // something that should run periodically }
For fixed-delay and fixed-rate tasks, an initial delay can be specified by indicating the amount of time to wait before executing the method for the first time, as shown in the fixedRate example below.
@Scheduled(initialDelay = 1000, fixedRate = 5000) public void doSomething() { // something that should run periodically }
If simple periodic scheduling is not expressive enough, cron expressions can be provided. The following example only runs on weekdays:
Starting with Spring Framework 4.3, bean s of any scope support @Scheduled methods.@Scheduled(cron="*/5 * * * * MON-FRI") public void doSomething() { // something that should run on weekdays only }
Make sure you are not initializing multiple instances of the same @Scheduled annotated class at runtime, unless you really want to schedule callbacks to each such instance. Related to this, make sure not to use @Configurationable on bean classes that are annotated with @Scheduled and registered as regular Spring beans in the container. Otherwise, you'll get two initializations (one through the container and one through the @Configurationable aspect), with the result that each @Scheduled method is called twice.
3.3 @Async annotation
You can provide @Async annotation on a method so that the method is called asynchronously. In other words, the caller returns immediately when called, and the actual execution of the method happens in a task that has been submitted to the Spring TaskExecutor. In the simplest case, annotations can be applied to methods returning void, as in the following example:
@Async void doSomething() { // this will be run asynchronously }
Unlike methods annotated with the @Scheduled annotation, these methods may require parameters because they are called in the "normal" way by the caller at runtime, not by a container-managed scheduled task. For example, the following code is a legal application of the @Async annotation:
@Async void doSomething(String s) { // this will be run asynchronously }
Even methods that return a value can be called asynchronously. However, such methods are required to have a return value of type Future. This still provides the benefit of asynchronous execution, so the caller can perform other tasks before calling get() on the Future. The following example shows how to use @Async on a method that returns a value:
@Asynchronous methods can declare not only the regular java.util.concurrent.Future return type, but also Spring's org.springframework.util.concurrent.ListenableFuture, or starting with Spring 4.2, JDK 8's java.util.coccurrent.CompletableFuture, so that Richer interaction with asynchronous tasks and immediate composition with further processing steps.@Async Future<String> returnSomething(int i) { // this will be run asynchronously }
You cannot combine @Async with lifecycle callbacks such as @PostConstruct. To initialize a Spring bean asynchronously, currently a separate initialization Spring bean must be used to invoke an @Async annotated method on the target, as shown in the following example:
public class SampleBeanImpl implements SampleBean { @Async void doSomething() { // ... } } public class SampleBeanInitializer { private final SampleBean bean; public SampleBeanInitializer(SampleBean bean) { this.bean = bean; } @PostConstruct public void initialize() { bean.doSomething(); } }
3.4 Executor Qualification with @Async
By default, when @Async is specified on a method, the executor used is the one configured when async support was enabled, i.e. an "annotation-driven" element if implemented using XML or the AsyncConfigurer (if any). However, when you need to indicate that an executor other than the default should be used when executing a given method, you can use the value attribute of the @Async annotation. The following example shows how to do this:
@Async("otherExecutor") void doSomething(String s) { // this will be run asynchronously by "otherExecutor" }
In this case, "otherExecutor" can be the name of any Executor bean in the Spring container, or the name of a qualifier associated with any Executor (specified, for example, using the <qualifier> element or Spring's @qualifier annotation).
3.5@Async exception management
When an @Async method has a return value of type Future, it is easy to manage exceptions thrown during method execution, as this exception is thrown when get is called on the Future result. However, for void return types, exceptions are uncaught and cannot be transmitted. You can provide an AsyncUnaughtExceptionHandler to handle such exceptions. The following example shows how to do this:
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { // handle exception } }
Code example:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.0.2.RELEASE</version> </dependency>
/** * Add the @EnableScheduling annotation to the startup class of spring boot */ @SpringBootApplication @EnableScheduling public class ScheduleApplication { public static void main(String[] args) { SpringApplication.run(ScheduleApplication.class,args); } }
Two other important attributes of the @Scheduled annotation: fixedRate and fixedDelay
fixedDelay: how long to execute the next task after the previous task ends
fixedRate: the interval from the start of the previous task to the start time of the next task
@Component public class ScheduleDoker { /** * Test fixedRate, execute every 2s * @throws Exception */ @Scheduled(fixedRate = 2000) public void fixedRate() throws Exception { System.out.println("fixedRate start execution time:" + new Date(System.currentTimeMillis())); //Sleep for 1 second Thread.sleep(1000); System.out.println("fixedRate execution end time:" + new Date(System.currentTimeMillis())); } fixedRate start execution time:Sun Feb 12 19:59:05 CST 2022 fixedRate execution end time:Sun Feb 12 19:59:06 CST 2022 fixedRate start execution time:Sun Feb 12 19:59:07 CST 2022 fixedRate execution end time:Sun Feb 12 19:59:08 CST 2022 fixedRate start execution time:Sun Feb 12 19:59:09 CST 2022 fixedRate execution end time:Sun Feb 12 19:59:10 CST 2022 /** * Wait for the last execution to wait for 1s to execute * @throws Exception */ @Scheduled(fixedDelay = 1000) public void fixedDelay() throws Exception { System.out.println("fixedDelay start execution time:" + new Date(System.currentTimeMillis())); //Sleep for two seconds Thread.sleep(1000 * 2); System.out.println("fixedDelay execution end time:" + new Date(System.currentTimeMillis())); } fixedDelay execution end time:Sun Feb 12 13:07:23 CST 2022 fixedDelay start execution time:Sun Feb 12 13:07:24 CST 2022 fixedDelay execution end time:Sun Feb 12 13:07:26 CST 2022 fixedDelay start execution time:Sun Feb 12 13:07:27 CST 2022 fixedDelay execution end time:Sun Feb 12 13:07:29 CST 2022 }
Four, task namespace
Since version 3.0, Spring includes an XML namespace for configuring TaskExecutor and TaskScheduler instances. It also provides a convenient way to configure tasks to be scheduled using triggers
Five, cron expression
All Spring cron expressions must conform to the same format, whether you are in a @Scheduled annotation, task:scheduled element, or elsewhere. A well-formed cron expression (such as ) consists of six space-separated time and date fields, each with its own range of valid values: * * * * * *
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
│ │ │ │ │ │
* * * * * *
There are some rules that apply:
Fields can be an asterisk (*), which always stands for "first-last". You can use a question mark (?) instead of an asterisk for a day of month or sunday field.
Commas (,) are used to separate items in a list.
Two numbers separated by a hyphen (-) represent a series of numbers. The specified range is inclusive.
After a range with a / (or *), specify the interval of numeric values within that range.
English names can also be used for the month and day of week fields. Use the first three letters of a specific day or month (case does not matter).
The "Monday" and "Sunday" fields can contain L characters, which have different meanings.
In the month day field, L represents the last day of the month. If followed by a negative offset (i.e. L-n), it means the nth to the last day of the month.
In the day of the week field, L represents the last day of the week. If prefixed with a number or a three-letter name (dL or DDDL), it indicates the last day of the month (d or DDD).
The "day of month" field can be nW, which represents the nearest weekday of the month. If n falls on a Saturday, this will yield the previous Friday. If n is on a Sunday, this will generate the following Monday, which will also happen if n is 1 and falls on a Sunday (ie: 1W represents the first weekday of the month).
If the month day field is LW, it means the last working day of the month.
The day of the week field can be d#n (or DDD#n), indicating the nth week d (or DDD) in a month.
Here are some examples:
Cron expression | significance |
0 0 * * * * | top of every hour |
*/10 * * * * * | every ten seconds |
0 0 8-10 * * * | 8:00, 9:00 and 10:00 every day |
0 0 6,19 * * * | 6:00am and 7:00pm daily |
0 0/30 8-10 * * * | Daily at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 |
0 0 9-17 * * MON-FRI | On the hour from nine to five on weekdays |
0 0 0 25 DEC ? | every christmas midnight |
0 0 0 L * * | Midnight on the last day of the month |
0 0 0 L-3 * * | Midnight on the third last day of the month |
0 0 0 * * 5L | Midnight on the last Friday of every month |
0 0 0 * * THUL | Midnight on the last Thursday of every month |
0 0 0 1W * * | Midnight on the first working day of the month |
0 0 0 LW * * | Midnight on the last working day of the month |
0 0 0 ? * 5#2 | Midnight on the second Friday of each month |
0 0 0 ? * MON#1 | First Monday of every month at midnight |
5.1. Macros
Expressions like 0 0*** are hard for humans to parse, and therefore hard to fix when they go wrong. For readability, Spring supports the following macros, which represent commonly used sequences. You can use these macros instead of six-digit values, for example: @Scheduled(cron="@hourly").
macro | significance |
@yearly (or @annually) | Once a year (0 0 0 1 1 *) |
@monthly | Once a month (0 0 0 1 * *) |
@weekly | once a week (0 0 0 * * 0) |
@daily (or @midnight) | once a day (), or 0 0 0 * * * |
@hourly | Once an hour, (0 0 * * * *) |