How to Schedule Tasks with Spring Boot

In this article, You’ll learn how to schedule tasks in Spring Boot using @Scheduled annotation. You’ll also learn how to use a custom thread pool for executing all the scheduled tasks.

The @Scheduled annotation is added to a method along with some information about when to execute it, and Spring Boot takes care of the rest.

Spring Boot internally uses the TaskScheduler interface for scheduling the annotated methods for execution.

The purpose of this article is to build a simple project demonstrating all the concepts related to task scheduling.

Create the Project

Let’s use Spring Boot CLI to create the Project. Fire up your terminal and type the following command to generate the project -

$ spring init --name=scheduler-demo scheduler-demo 

Alternatively, You can generate the project using Spring Initializer web app. Just go to http://start.spring.io/, enter the Artifact’s value as “scheduler-demo” and click Generate to generate and download the project.

Once the project is generated, import it in your favorite IDE. The project’s directory structure should like this -

Spring Boot Scheduled Annotation Example Directory Structure"

Enable Scheduling

You can enable scheduling simply by adding the @EnableScheduling annotation to the main application class or one of the Configuration classes.

Open SchedulerDemoApplication.java and add @EnableScheduling annotation like so -

package com.example.schedulerdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class SchedulerDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(SchedulerDemoApplication.class, args);
	}
}

Scheduling Tasks

Scheduling a task with Spring Boot is as simple as annotating a method with @Scheduled annotation, and providing few parameters that will be used to decide the time at which the task will run.

Before adding tasks, Let’s first create the container for all the scheduled tasks. Create a new class called ScheduledTasks inside com.example.schedulerdemo package with the following contents -

package com.example.schedulerdemo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

@Component
public class ScheduledTasks {
    private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");

    public void scheduleTaskWithFixedRate() {}

    public void scheduleTaskWithFixedDelay() {}

    public void scheduleTaskWithInitialDelay() {}

    public void scheduleTaskWithCronExpression() {}
}

The class contains four empty methods. We’ll look at the implementation of all the methods one by one.

All the scheduled methods should follow the following two criteria -

  • The method should have a void return type.
  • The method should not accept any arguments.

Cool! Let’s now jump into the implementation.

1. Scheduling a Task with Fixed Rate

You can schedule a method to be executed at a fixed interval by using fixedRate parameter in the @Scheduled annotation. In the following example, The annotated method will be executed every 2 seconds.

@Scheduled(fixedRate = 2000)
public void scheduleTaskWithFixedRate() {
    logger.info("Fixed Rate Task :: Execution Time - {}", dateTimeFormatter.format(LocalDateTime.now()) );
}
# Sample Output
Fixed Rate Task :: Execution Time - 10:26:58
Fixed Rate Task :: Execution Time - 10:27:00
Fixed Rate Task :: Execution Time - 10:27:02
....
....

The fixedRate task is invoked at the specified interval even if the previous invocation of the task is not finished.

2. Scheduling a Task with Fixed Delay

You can execute a task with a fixed delay between the completion of the last invocation and the start of the next, using fixedDelay parameter.

The fixedDelay parameter counts the delay after the completion of the last invocation.

Consider the following example -

@Scheduled(fixedDelay = 2000)
public void scheduleTaskWithFixedDelay() {
    logger.info("Fixed Delay Task :: Execution Time - {}", dateTimeFormatter.format(LocalDateTime.now()));
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException ex) {
        logger.error("Ran into an error {}", ex);
        throw new IllegalStateException(ex);
    }
}

Since the task itself takes 5 seconds to complete and we have specified a delay of 2 seconds between the completion of the last invocation and the start of the next, there will be a delay of 7 seconds between each invocation -

# Sample Output
Fixed Delay Task :: Execution Time - 10:30:01
Fixed Delay Task :: Execution Time - 10:30:08
Fixed Delay Task :: Execution Time - 10:30:15
....
....

3. Scheduling a Task With Fixed Rate and Initial Delay

You can use initialDelay parameter with fixedRate and fixedDelay to delay the first execution of the task with the specified number of milliseconds.

In the following example, the first execution of the task will be delayed by 5 seconds and then it will be executed normally at a fixed interval of 2 seconds -

@Scheduled(fixedRate = 2000, initialDelay = 5000)
public void scheduleTaskWithInitialDelay() {
    logger.info("Fixed Rate Task with Initial Delay :: Execution Time - {}", dateTimeFormatter.format(LocalDateTime.now()));
}
# Sample output (Server Started at 10:48:46)
Fixed Rate Task with Initial Delay :: Execution Time - 10:48:51
Fixed Rate Task with Initial Delay :: Execution Time - 10:48:53
Fixed Rate Task with Initial Delay :: Execution Time - 10:48:55
....
....

4. Scheduling a Task using Cron Expression

If the above simple parameters can not fulfill your needs, then you can use cron expressions to schedule the execution of your tasks.

In the following example, I have scheduled the task to be executed every minute -

@Scheduled(cron = "0 * * * * ?")
public void scheduleTaskWithCronExpression() {
    logger.info("Cron Task :: Execution Time - {}", dateTimeFormatter.format(LocalDateTime.now()));
}
# Sample Output
Cron Task :: Execution Time - 11:03:00
Cron Task :: Execution Time - 11:04:00
Cron Task :: Execution Time - 11:05:00

Running @Scheduled Tasks in a Custom Thread Pool

By default, all the @Scheduled tasks are executed in a default thread pool of size one created by Spring.

You can verify that by logging the name of the current thread in all the methods -

logger.info("Current Thread : {}", Thread.currentThread().getName());

All the methods will print the following -

Current Thread : pool-1-thread-1

But hey, You can create your own thread pool and configure Spring to use that thread pool for executing all the scheduled tasks.

Create a new package config inside com.example.schedulerdemo, and then create a new class called SchedulerConfig inside config package with the following contents -

package com.example.schedulerdemo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
    private final int POOL_SIZE = 10;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

        threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
        threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
        threadPoolTaskScheduler.initialize();

        scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
    }
}

That’s all you need to do for configuring Spring to use your own thread pool instead of the default one.

If you log the name of the current thread in the scheduled methods now, you’ll get the output like so -

Current Thread : my-scheduled-task-pool-1
Current Thread : my-scheduled-task-pool-2

# etc...

Conclusion

In this article, you learned how to schedule tasks in Spring Boot using @Scheduled annotation. You also learned how to use a custom thread pool for running these tasks.

You can find the complete code for the project that we built in this article in my github repository.

The Scheduling abstraction provided by Spring Boot works pretty well for simple use-cases. But if you have more advanced use cases like Persistent Jobs, Clustering, Dynamically adding and triggering new jobs then check out the following article -

Spring Boot Quartz Scheduler Example: Building an Email Scheduling App

Thank you for reading. See you in the next Post!