Spring task scheduler is either configured with @scheduled
annotation or dynamically with Java code, to schedule a task. @EnableScheduling
is required to enable support for Spring task scheduling. Spring task scheduling can be a challenge in clustered environments, where multiple instances of the same application run. We will discuss each one of these challenges here.
Spring task scheduling for a task/job is a time based scheduled process which runs automatically as per the configuration supplied. Once scheduling is enabled in application, a scheduled task can execute automatically in Spring application. Scheduled task is scheduled with annotation as well as with Java code, which supports time based scheduling including cron expression.
- Spring task scheduler annotation
- Enable Support for Scheduling
- Spring task scheduler example with Fixed Delay
- Spring task scheduler example with Fixed Rate
- Spring task scheduler example with initial Delay
- Schedule a task with Spring programmatically
- Dynamically Schedule Spring tasks to run later
- Spring scheduler custom thread pool
- Spring scheduler cron example
- Spring scheduler dynamic cron expression from properties file
- Spring scheduler load cron expression from database
- Spring scheduler in clustered environment
- Source code for examples
- Conclusion
Spring task scheduler annotation
Spring provides @Scheduled
annotation for task scheduling which supports execution based upon cron expression as well as in built attributes for simple configuration. A method can be converted to scheduled task by adding @Scheduled
annotation on top of method declaration. The @Scheduled
annotation is triggered using the metadata supplied for task schedule as annotation attributes, at least one of these attributes cron(), fixedDelay() or fixedRate() must to be specified.
Enable Support for Scheduling
For task scheduling in Spring, you need to enable the support of scheduling in application. Spring provides @EnableScheduling
annotation for this purpose The @EnableScheduling
annotation is required to be used with either @Configuration or @SpringBootApplication
annotated class only. For example, in following code snippet, we have added the annotation @EnableScheduling
at class level:
@SpringBootApplication @EnableScheduling public class SchedulerApplication { public static void main(String[] args) { SpringApplication.run(SchedulerApplication.class, args); } }
Spring task scheduler example with Fixed Delay
The @Scheduled
annotation has a fixedDelay attribute, which accepts the attribute values as long data type and treats the value as milliseconds. The fixedDelay attribute is required to trigger the execution of an annotated method with a fixed period of time (in milliseconds) between the start of new execution and the end of the last execution.
For example, if you specify fixedDelay as 5000 (i.e. 5 seconds) and consider our execution will take around 2 seconds. The first task execution will start at time 00:00 and finish at 00:02. Then the next execution will start after 5 seconds of the end of earlier execution, which in this case is 00:02, that means the next execution will happen at 00:07.
Following is a code example to schedule a task at fixedDelay of 5 seconds.
@Component public class ScheduledTasksFixedDelay { final static Logger logger = LoggerFactory.getLogger(ScheduledTasksFixedDelay.class); @Scheduled(fixedDelay = 5000) public void scheduleTaskFixedDelay(){ logger.info("scheduleTaskFixedDelay executed at {}", LocalDateTime.now()); } }
Spring task scheduler example with Fixed Rate
The @Scheduled
annotation also has a fixedRate attribute, which accepts the attribute values as long data type and treats the value as a milliseconds unit. The fixedRate executes the annotated method after a fixed period in milliseconds between invocations, it doesn’t check whether last execution is complete or not, it just triggers the new invocation after the fixed period.
For example, if you specify fixedRate as 10000 (i.e. 10 seconds) and consider our execution will take around 2 seconds. The first task execution will start at time 00:00 and finish at 00:02. Then the next execution will start after the next 10 seconds ignoring the last execution time, that means the next execution will happen at 00:10.
@Component public class ScheduledTasksFixedRate { final static Logger logger = LoggerFactory.getLogger(ScheduledTasksFixedRate.class); @Scheduled(fixedRate = 10000) public void scheduleTaskFixedRate(){ logger.info("scheduleTaskFixedRate executed at {}", LocalDateTime.now()); } }
Spring task scheduler example with initial Delay
The initialDelay attribute specifies the delay in first execution of a task with fixedRate or fixedDelay configuration. The initialDelay is an optional attribute and its value is specified in milliseconds as long type. The initialDelay can’t be used alone, it is used either with fixedRate or fixedDelay attributes.
For example if you specify the initialDelay as 2000 i.e. 2 seconds and fixedDelay as 5000 i.e. 5 seconds. Suppose your application startup time is 12:00 then this job will first be executed at 12:02. If we remove the initialDelay attribute here then the first execution time becomes 12:00 itself.
@Component public class ScheduledTasksInitialDelay { final static Logger logger = LoggerFactory.getLogger(ScheduledTasksInitialDelay.class); @Scheduled(initialDelay = 2000, fixedDelay = 5000) public void scheduleTaskInitialAndFixedDelay(){ logger.info("scheduledTaskInitialAndFixedDelay executed at {}", LocalDateTime.now()); } }
Schedule a task with Spring programmatically
Dynamic task scheduling with Spring can be using ScheduledTaskRegistrar class. The ScheduledTaskRegistrar is a helper class for registering tasks with a TaskScheduler, typically using cron expressions but also able to configure fixedRate or fixedDelay tasks programmatically.
For scheduling a task programmatically, first create a class which implements an interface SchedulingConfigurer. Override its configureTasks method to schedule the tasks dynamically. Inside this overridden method, you can access the object of the ScheduledTaskRegistrar bean. ScheduledTaskRegistrar object has multiple methods to configure fixedRate, fixedDelay and cron expressions based tasks. Select any of these methods and provide your method wrapped inside the Runnable interface. If you want to specify the time interval then pass seconds argument as a long value of milliseconds.
In the following example, we have scheduled a task programmatically with a fixedRate of 5 seconds. Our method is a normal method, which is wrapped inside a Runnable interface.
@Component public class DynamicTasksScheduled implements SchedulingConfigurer { private static Logger logger = LoggerFactory.getLogger(DynamicTasksScheduled.class); @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); taskRegistrar.addFixedRateTask((new Runnable() { @Override public void run() { dynamicTasksSchedule(); } }), 5000); } public Executor taskExecutor() { return Executors.newScheduledThreadPool(50); } public void dynamicTasksSchedule() { logger.info("dynamicTasksSchedule executed at {}", LocalDateTime.now()); } }
Dynamically Schedule Spring tasks to run later
Sometimes we need to delay some processing part which depends upon some other processing part and need some gap before processing. In enterprise applications, this is mostly commonly scenario and you can’t use Java threads everywhere to solve this problem.
For example, after save/update operation, we need to inform some micro-service via events that latest data is available to be used. This example seems to be simple, but it includes a lot of complexity. As soon as we can save method then we will trigger event, save method calls internally database commit asynchronously. So in some cases, event may triger first while commit is not complete and during that time our micro-service may read the data. This will create an issue as micro-service is expecting new data but due to commit which is unfinished, our flow will break.
In Following example, we are scheduling a task to be executed after 5 seconds. We are using @PostContruct to simulate it as run server startup, but it can done dynamically as well.
public class DynamicTasknOneTimeRun { private static Logger logger = LoggerFactory.getLogger(DynamicTasknOneTimeRun.class); @Autowired private TaskScheduler taskScheduler; @PostConstruct public void scheduleTasks() { taskScheduler.schedule(() -> { dynamicTasksScheduleThreadPool(); }, new Date(System.currentTimeMillis() + 5000)); } public void dynamicTasksScheduleThreadPool() { logger.info("DynamicTasknOneTimeRun executed at {}", LocalDateTime.now()); } }
Spring scheduler custom thread pool
Instead of using ScheduledTaskRegistrar’s TaskScheduler, you can create your own custom ThreadPoolTaskScheduler and configure it as per your requirements (like machine dependency, threshold for maximum threads etc). First of all, you should create a bean of ThreadPoolTaskScheduler and configure it like following:
@Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler(){ ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(5); threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler"); return threadPoolTaskScheduler; }
Then create a service class where you will inject this custom ThreadPoolTaskScheduler bean. ThreadPoolTaskScheduler again will provide different methods of scheduling a task with Spring. In this example, we will schedule a task with fixedDelay. Since ThreadPoolTaskScheduler is based upon the Java executor framework, we again need to pass a Runnable interface. We can either create a class which can implement Runnable interface or wrap our method with Runnable interface on the fly.
For example, in the below snippet, I have added this logic inside a method which I have named as scheduleTasks. But this task scheduling logic will not work until we call this method to invoke. For that purpose, I have annotated the scheduleTasks method with annotation @PostConstruct
. When Spring initializes this bean then its @PostContruct
method will be called once, which will execute our code and schedule our task.
@Service public class DynamicTasksScheduledThreadPool { private static Logger logger = LoggerFactory.getLogger(DynamicTasksScheduledThreadPool.class); @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; @PostConstruct public void scheduleTasks(){ threadPoolTaskScheduler.scheduleWithFixedDelay( (new Runnable() { public void run() { dynamicTasksScheduleThreadPool(); } }), 3000); } public void dynamicTasksScheduleThreadPool(){ logger.info("dynamicTasksScheduleThreadPool executed at {}", LocalDateTime.now()); } }
Spring scheduler cron example
Spring supports task scheduling with cron expressions, a cron expression is a string of 6 or 7 fields separated by white spaces and collectively represent a time schedule. Every field can have any value out of allowed ones and collectively all fields any kind of combination to represent yearly, monthly, weekly, daily, hourly or even lower time schedule.
Spring task scheduler can accept cron expressions and can trigger the invocation of a method at the correct time interval. A cron expression is interpreted from left to right manner in following sequence:
- second
- minute
- hour
- day of month
- month
- day of week
In our example snippet, we have used the cron expression “*/3 * * ? * *“, which represents a time interval of 3 seconds. It means after every 3 seconds, our method will be invoked by the Spring task scheduler.
@Component public class ScheduledTasksCron { final static Logger logger = LoggerFactory.getLogger(ScheduledTasksCron.class); @Scheduled(cron = "*/3 * * ? * *") public void scheduleCronTask(){ logger.info("scheduleCronTask executed at {}", LocalDateTime.now()); } }
Spring scheduler dynamic cron expression from properties file
In spring projects, a common requirement remains to read from properties file for better readability. You can schedule a cron expression based task in spring, even mentioning the cron expression in properties file.
For example, in properties file lets add following key value pair.
scheduledTasksCronPropertiesConfig.cron.expression=*/15 * * ? * *
Here value represents a cron expression, which represents to execute after every 15 seconds and can be accessed by the key name “scheduledTasksCronPropertiesConfig.cron.expression”
To use this key from properties file as cron expression, we have to use notation from SpEL (Spring Expression Language). For example, in the following snippet, we are telling Spring that string starting with $ sign and wrapped inside curly brackets is a property placeholder. So spring will try to resolve this property from the properties file.
@Scheduled(cron="${scheduledTasksCronPropertiesConfig.cron.expression}")
Following is the full example, for dynamic cron expression to be loaded from properties file.
@Component public class ScheduledTasksCronPropertiesConfig { final static Logger logger = LoggerFactory.getLogger(ScheduledTasksCronPropertiesConfig.class); @Scheduled(cron="${scheduledTasksCronPropertiesConfig.cron.expression}") public void scheduledTasksCronPropertiesConfig(){ logger.info("scheduledTasksCronPropertiesConfig executed at {}", LocalDateTime.now()); } }
Spring scheduler load cron expression from database
Spring tasks scheduling sometimes depends upon the database to load cron expressions, especially when you have many scheduled tasks. Then you will prefer to manage the cron expressions in a special way and may create one admin page on cron expressions with CRUD operations. This means that cron expression will come from database now, so we need a way to automatically load the required cron expression from database by Spring while doing task scheduling.
Lets create a domain entity class to hold our cron expressions in the database and name it as “Configuration” (you can use any name).
@Entity public class Configuration { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String name; @Column private String expression; // add getters and setters here }
Now lets create its repository class which will have one JPA method to find by name.
@Repository public interface ConfigurationRepository extends JpaRepository<Configuration, Long> { Configuration findByName(String name); }
We will be using the H2 database for demonstration and will configure it by Liquibase changesets, however you can run the SQL scripts manually.
CREATE TABLE configuration ( id int, name VARCHAR(64), expression VARCHAR(64), PRIMARY KEY (id) ); insert into configuration(id, name, expression) values(1, 'scheduleTasksCronFromDatabaseExpression','*/1 * * ? * *');
We have inserted a record with the name of expression as ‘scheduleTasksCronFromDatabaseExpression’, which will be used to fetch this record.
Next step is to create a Spring Task scheduler. Here, we will be using SpEL (Spring expression Language), as it is the simplest way to call your java code with Spring annotations. Example of this cron expression will be like this:
@Scheduled(cron="#{@getCronExpressionFromDb}")
Here the string mentioned is getCronExpressionFromDb, which is actually a Java method. It is wrapped inside # with curly brackets and starts with @, this way Spring understands that its a java method which needs to be called. Now inside the getCronExpressionFromDb method, write the logic to read from the database using your service or repository directly. In our example, we have autowired ‘ConfigurationRepository’ and called its fineByName method to fetch ‘Configuration’ object and return its ‘expression’ property.
@Component public class ScheduledTasksCronFromDatabaseExpression { final static Logger logger = LoggerFactory.getLogger(ScheduledTasksCronFromDatabaseExpression.class); @Autowired private ConfigurationRepository configurationRepository; @Scheduled(cron = "#{@getCronExpressionFromDb}") public void scheduleTasksCronFromDatabaseExpression() { logger.info("scheduleTasksCronFromDatabaseExpression executed at {}", LocalDateTime.now()); } @Bean public String getCronExpressionFromDb() { // read here from database and return Configuration configuration = configurationRepository.findByName("scheduleTasksCronFromDatabaseExpression"); return configuration.getExpression(); } }
Spring scheduler in clustered environment
The major challenge faced by developers is to limit the scheduled task or scheduled job to a single instance in a clustered environment. Generally, in a production environment, there are multiple instances of a single application running under a load balancer to handle scale and traffic efficiently.
Few available solutions
In a Spring application, if you have created some scheduled task then in a clustered environment that task will start executing on each and every search wherever the application instance is running. So if your job does some database job to do some operations, then you can be in trouble because the same operations will be triggered by every application instance. So we need some kind of mechanism to restrict the task execution to a single instance. There are a number of libraries available which can help you here. Few of them are following:
- Shedlock
- db-scheduler
- Easy-Batch
- JobRunr
- dlock
Out of the above list, we will discuss only one library which is Shedlock. Shedlock is widely accepted and provides out of the box features.
Shedlock Example
Shedlock provides the ability to restrict the task execution to one invocation. Shedlock uses external storage like JDBC databases, Mongo, Hazelcast, Redis etc. If a task starts execution then first of all it acquires a lock (storage based), which prevents the execution of the same task from other application instances. If a task from another instance is unable to acquire lock then it doesn’t wait for acquiring lock, it’s simply skipped.
Shedlock Gradle dependencies
To add Shedlock into your spring project, add following two dependencies into your build.gradle file
compile 'net.javacrumbs.shedlock:shedlock-spring:4.14.0' compile 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:4.14.0'
Shedlock Configuration
Since Shedlock uses the lock mechanism using storage. In our example we will use the H2 database as a datasource. Create a table with the following SQL into your database or use Liquibase to run your SQL script automatically. In our example, we will use a Liquibase changeset, but you can run this SQL script manually also.
CREATE TABLE shedlock ( name VARCHAR(64), lock_until TIMESTAMP(3) NULL, locked_at TIMESTAMP(3) NULL, locked_by VARCHAR(255), PRIMARY KEY (name) )
Now, configure datasource for ShedLock, so it can use the Spring database configuration for read/write to the table.
@Configuration @EnableSchedulerLock(defaultLockAtMostFor = "10m") public class ShedlockConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(new JdbcTemplate(dataSource)) .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2 .build() ); } }
Shedlock configuration is complete now, it’s very minimum steps to configure. Next step is to use Shedlock with task scheduling. For that create a Spring task Scheduler and annotate it with ShedLock’s annotation @SchedulerLock
. In annotation @SchedulerLock
, you need to pass a unique name of that particular task. ShedLock uses this task name to grant and restrict rights over multiple instances and saves all information in the database.
@Component public class ScheduledTasksShedlock { final static Logger logger = LoggerFactory.getLogger(ScheduledTasksShedlock.class); @Scheduled(fixedDelay = 5000) @SchedulerLock(name = "scheduledTasksShedlock") public void scheduledTasksShedlock(){ logger.info("scheduledTasksShedlock executed at {}", LocalDateTime.now()); } }
Now if you run this task on a single machine, there is no difference. But if you run this task in a clustered environment, you will see only a single task gets executed from a single instance, not from all instances.
You can read more details on Shedlock’s home page.
Source code for examples
All the code snippets used in this article are present in our github repository. You can download the source from github repository and feel free to use it in any way.
Conclusion
In this article, you have learned the different ways to schedule a task in Spring with different types of configurations. You can pull the configuration from the properties file or database and most importantly manage the clustered environment for running a single task to avoid duplicate tasks execution.
Pingback: Spring Server-Sent Events