Task scheduling is frequently used in web applications to execute different jobs at certain times without any manual input. Examples include backing up the database, sending newsletter emails, deleting log files, moving files from one server to another server, just to name a few.
Spring Boot provides multiple ways to schedule tasks. You can either use @Scheduled
annotation or use a custom thread pool to run your tasks at specific times.
In this article, we will learn how to schedule tasks in a Spring Boot application using @Scheduled
annotation. We will also take a look at using a custom thread pool to execute scheduled tasks.
Dependencies
We only need spring-boot-starter
dependency to use the @Scheduled
annotation. Add the following to your build.gradle
file:
implementation 'org.springframework.boot:spring-boot-starter'
If you are using Maven, add the following dependency to your pom.xml
file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Enable Scheduling
We can enable the scheduling functionality by adding @EnableScheduling
annotation to the main application class or any other configuration class like below:
Application.java
package com.attacomsian.scheduling;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableScheduling
annotation ensures that a background task executor is created with a single thread.
Scheduling Tasks
Scheduling a task is easy. Simply add the @Scheduled
annotation to any method you want to schedule and set the time it should execute. However, all such methods must meet the following two conditions:
- The method return type should be void (
void
) - The method should not accept any parameter
That being said, let us create a Java class that will act as a container to hold all our scheduled tasks:
ScheduledTasks.java
package com.attacomsian.scheduling;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.format.DateTimeFormatter;
@Component
public class ScheduledTasks {
private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
// add scheduled methods here
}
Schedule Task with Fixed Rate
Let us schedule our first task that executes at a fixed interval of time by using fixedRate
property in the @Scheduled
annotation:
@Scheduled(fixedRate = 2500)
public void scheduleTaskWithFixedRate() {
logger.info("Fixed Rate Task: Current Time - {}", formatter.format(LocalDateTime.now()));
}
The above task is scheduled to execute every 3 seconds and outputs the following on the console:
Fixed Rate Task: Current Time - 13:36:53
Fixed Rate Task: Current Time - 13:36:56
Fixed Rate Task: Current Time - 13:36:58
Fixed Rate Task: Current Time - 13:37:01
...
The fixedRate
task is invoked for every specified interval. The specified interval between method invocations is measured from the start time of each invocation. In other words, the task is executed again even if the previous invocation of the task is not completed.
This option is best suited when each execution of the task is independent. The
fixedRate
property executes the task at every n milliseconds. It does not wait for any previous execution to finish.
Schedule Task with Fixed Delay
Very similar to fixedRate
, the fixedDelay
task is invoked for every specified interval but the time is measured from the completion time of each preceding invocation.
In short, the time between the end of the last execution and the start of the next execution is constant. The task always waits until the previous one is completed.
Consider the following example:
@Scheduled(fixedDelay = 2000)
public void scheduleTaskWithFixedDelay() throws InterruptedException {
logger.info("Fixed Delay Task: Start Time - {}", formatter.format(LocalDateTime.now()));
// add some virtual processing time
TimeUnit.SECONDS.sleep(3);
logger.info("Fixed Delay Task: End Time - {}", formatter.format(LocalDateTime.now()));
}
The task is scheduled to execute every 2 seconds and prints the start and finish times of the execution. Since we have added 3 seconds of virtual processing time, it will take at least 3 seconds to complete. There will be a delay of 5 seconds between successive invocations:
Fixed Delay Task: Start Time - 14:02:28
Fixed Delay Task: End Time - 14:02:31
Fixed Delay Task: Start Time - 14:02:33
Fixed Delay Task: End Time - 14:02:36
Fixed Delay Task: Start Time - 14:02:38
Fixed Delay Task: End Time - 14:02:41
Fixed Delay Task: Start Time - 14:02:43
Fixed Delay Task: End Time - 14:02:46
...
This option is best suited when the previous execution of the task must be completed before executing it again. The
fixedDelay
property makes sure that is always a delay of n milliseconds between consecutive invocations of a task. For dependent tasks, it is pretty helpful.
Schedule Task with Initial Delay
You can also specify the initial time to wait (in milliseconds) before the first execution of the task begins by using the initialDelay
property. It works with both fixedRate
and fixedDelay
properties.
In the following example, the task is executed the first time after a wait of 5 seconds and then it executes normally after every 2 seconds:
@Scheduled(fixedRate = 2000, initialDelay = 5000)
public void scheduleTaskWithFixedRateAndInitialDelay() {
logger.info("Fixed Rate Task with Initial Delay: Current Time - {}", formatter.format(LocalDateTime.now()));
}
The initialDelay
property delays the first execution of the task for the specified milliseconds. After the first execution, the task starts executing normally:
# Server started at 14:42:20
Fixed Rate Task with Initial Delay: Current Time - 14:42:25
Fixed Rate Task with Initial Delay: Current Time - 14:42:27
Fixed Rate Task with Initial Delay: Current Time - 14:42:29
Fixed Rate Task with Initial Delay: Current Time - 14:42:31
...
The
initialDelay
property helps delay the first execution of the task until the data required to execute the tasks is provided by some other services.
Schedule Task using Cron Expression
Sometimes the fixed-rate and fixed-delay are not enough to fulfill our needs. We want more flexibility to control the schedule of our tasks. For example, we might want to send a newsletter every Thursday or back up our database every week. This is something that cannot be done with the above properties.
That is where the cron expressions come handy. They provide complete flexibility to schedule the tasks, whatever the way you want to choose.
Here is an example task that uses cron expression to execute every minute:
@Scheduled(cron = "0 * * * * ?")
public void scheduleTaskWithCronExpression() {
logger.info("Cron Task: Current Time - {}", formatter.format(LocalDateTime.now()));
}
Cron Task: Current Time - 15:17:00
Cron Task: Current Time - 15:18:00
Cron Task: Current Time - 15:19:00
Cron Task: Current Time - 15:20:00
...
Let us have a few more examples of cron expressions. The following tasks are scheduled to be executed at 4:45 AM on the 10th day of every month:
@Scheduled(cron = "0 45 4 10 * ?")
public void scheduleTaskWithCronExpression2() {
logger.info("Cron Task: Current Time - {}", formatter.format(LocalDateTime.now()));
}
Cron expressions are somehow complex to write and understand. crontab.guru is a nice little tool that makes it easy to generate cron schedule expressions:
# At 12:00 on Sunday
0 0 12 * * Sun
# At 14:15 in every 2nd month
0 15 14 * */2 *
# At 08:00 on every day-of-week from Monday through Friday
0 0 8 * * Mon-Fri
# At 12:30 on every 15th day-of-month if it's on Wednesday
0 30 12 */15 * Wed
Parameterizing the Schedule
In the above examples, we have hard-coded the time intervals and cron expressions. Now if you want to change the execution time of any task, we have to recompile and redeploy the entire application. This is certainly not flexible.
Fortunately, we can make use of Spring Expression Language (SpPL) and store the configuration of the tasks in a properties file:
@Scheduled(fixedRateString = "${fixed-rate.in.milliseconds}")
public void scheduleDynamicTaskWithFixedRate() {
logger.info("Fixed Rate Dynamic Task: Current Time - {}", formatter.format(LocalDateTime.now()));
}
@Scheduled(fixedDelayString = "${fixed-delay.in.milliseconds}")
public void scheduleDynamicTaskWithFixedDelay() {
logger.info("Fixed Delay Dynamic Task: Current Time - {}", formatter.format(LocalDateTime.now()));
}
@Scheduled(cron = "${cron.expression}")
public void scheduleDynamicTaskWithCronExpression() {
logger.info("Cron Dynamic Task: Current Time - {}", formatter.format(LocalDateTime.now()));
}
And our application.properties
file will look like this:
application.properties
fixed-rate.in.milliseconds=5000
fixed-delay.in.milliseconds=4000
cron.expression=0 15 5 * * FRI
Custom Thread Pool Configuration
By default, @EnableScheduling
annotation creates a thread pool with only one thread. The invocation of all @Scheduled
tasks is queued and executed by an only thread. So if you have multiple scheduled tasks in your application, you might see weird behavior of invocation (since the tasks are queued).
But the good thing is you can create your own custom thread pool with multiple threads and configure the application to use that for executing all scheduled tasks:
SchedulerConfig.java
package com.attacomsian.scheduling;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
public class SchedulerConfig implements SchedulingConfigurer {
@Value("${thread.pool.size}")
private int POOL_SIZE;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(POOL_SIZE);
scheduler.setThreadNamePrefix("my-scheduled-task-pool-");
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
}
Add the following property to your application.properties
file:
thread.pool.size=10
Spring will now create a custom thread pool with 10 threads to execute the tasks. You can also find the thread name in log that was invoked to execute the task:
[ My-Scheduler-4] : Fixed Delay Dynamic Task: Current Time - 17:20:03
[ My-Scheduler-8] : Fixed Rate Dynamic Task: Current Time - 17:20:04
[ My-Scheduler-1] : Fixed Delay Dynamic Task: Current Time - 17:20:07
[ My-Scheduler-7] : Fixed Rate Dynamic Task: Current Time - 17:20:09
[ My-Scheduler-2] : Fixed Delay Dynamic Task: Current Time - 17:20:11
...
Source code: Download the complete source code from GitHub available under MIT license.
Conclusion
That's all folks for task scheduling in Spring Boot. We have learned how to schedule tasks in Spring Boot using @Scheduled
annotation. We also learned to create and use a custom thread pool for executing these tasks.
Task scheduling is much valuable to automate complex tasks that would have taken a lot of time doing manually. It saves time that can be spent on other productive things instead of worrying about routine tasks.
✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.