In this article, we will look at how to schedule tasks using the . Quartz is the de facto standard of scheduling libraries for Java applications. Quartz supports running jobs at a particular time, repeating job executions, storing jobs in a database, and Spring integration. Quartz framework Spring-annotations for scheduling The easiest way to use Quartz in Spring applications is to use the annotation. Next, we will consider an example of a Spring Boot application. Let's add the necessary dependency in @Scheduled build.gradle implementation 'org.springframework.boot:spring-boot-starter-quartz' and consider an example package quartzdemo.tasks; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Date; @Component public class PeriodicTask { @Scheduled(cron = "0/5 * * * * ?") public void everyFiveSeconds() { System.out.println("Periodic task: " + new Date()); } } Also, for the annotation to work, you need to add a configuration with the annotation. @Scheduled @EnableScheduling package quartzdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); } } The result will be a text output in the console every five seconds. Periodic task: Thu Jul 07 18:24:50 EDT 2022 Periodic task: Thu Jul 07 18:24:55 EDT 2022 Periodic task: Thu Jul 07 18:25:00 EDT 2022 ... The annotation supports the following parameters: @Scheduled - Allows you to run a task at a specified fixed interval. fixedRate - Execute a task with a fixed delay between the completion of the last invocation and the start of the next. fixedDelay - The parameter is used with and to wait before the first execution of the task with the specified delay. initialDelay fixedRate fixedDelay - Set the task execution schedule using the cron-string. Also supports macros (or ), , , (or ), and . cron @yearly @annually @monthly @weekly @daily @midnight @hourly By default, , and are set in milliseconds. This can be changed using the parameter, setting the value from to . fixedRate fixedDelay initialDelay timeUnit NANOSECONDS DAYS Furthermore, you can use properties in @Scheduled annotation: application.properties cron-string=0/5 * * * * ? PeriodicTask.java @Component public class PeriodicTask { @Scheduled(cron = "${cron-string}") public void everyFiveSeconds() { System.out.println("Periodic task: " + new Date()); } } To use properties, you can utilize , , and parameters instead of , and accordingly. fixedRateString fixedDelayString initialDelayString fixedRate fixedDelay initialDelay Directly use Quartz In the previous example, we executed scheduled tasks, but at the same time, we could not dynamically set the start time of the job, or pass parameters to it. To solve these problems, you can directly use Quartz. The main Quartz interfaces are listed below: is an interface to be implemented by the classes that contain the business logic that we wish to have executed Job defines instances and data that are related to it JobDetails Job describes the schedule of job execution Trigger is the main Quartz interface that provides all manipulation and searching operations for jobs and triggers Scheduler To work with Quartz directly, it is not necessary to define a configuration with the annotation, and instead of the dependency, you can use . @EnableScheduling org.springframework.boot:spring-boot-starter-quartz org.quartz-scheduler:quartz Let's define SimpleJob class: package quartzdemo.jobs; import org.quartz.Job; import org.quartz.JobExecutionContext; import java.text.MessageFormat; public class SimpleJob implements Job { @Override public void execute(JobExecutionContext context) { System.out.println(MessageFormat.format("Job: {0}", getClass())); } } To implement interface, you need to implement only one method that accepts a parameter of type. contains information about the job instance, trigger, scheduler, and other information about the job execution. Job execute JobExecutionContext JobExecutionContext Now let's define an instance of the job: JobDetail job = JobBuilder.newJob(SimpleJob.class).build(); And create a trigger that will trigger after five seconds: Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5) .atZone(ZoneId.systemDefault()).toInstant()); Trigger trigger = TriggerBuilder.newTrigger() .startAt(afterFiveSeconds) .build(); Also, create a scheduler: SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); The is initialized in “stand-by” mode, so we have to invoke method: Scheduler start scheduler.start(); Now we can schedule the execution of the job: scheduler.scheduleJob(job, trigger); Farther, when creating , let's add additional data and use it when executing a Job: JobDetails QuartzDemoApplication.java @SpringBootApplication public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); onStartup(); } private static void onStartup() throws SchedulerException { JobDetail job = JobBuilder.newJob(SimpleJob.class) .usingJobData("param", "value") // add a parameter .build(); Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5) .atZone(ZoneId.systemDefault()).toInstant()); Trigger trigger = TriggerBuilder.newTrigger() .startAt(afterFiveSeconds) .build(); SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.start(); scheduler.scheduleJob(job, trigger); } } SimpleJob.java public class SimpleJob implements Job { @Override public void execute(JobExecutionContext context) { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String param = dataMap.getString("param"); System.out.println(MessageFormat.format("Job: {0}; Param: {1}", getClass(), param)); } } It is important to note that all values that are added to must be serializable. JobDataMap JobStore Quartz stores data about , , and other information in . By default, the in-memory is used. This means that if we have scheduled tasks and shut down the application (for example, when restarting or crashing) before they are fired, then they will never be executed again. Quartz also supports JDBC-JobStore for storing information in a database. JobDetail Trigger JobStore JobStore Before using JDBC-JobStore, it is necessary to create tables in the database that Quartz will use. By default, these tables are prefixed with . QRTZ_ The Quartz source code contains for creating tables for various databases, such as Oracle, Postgres, MS SQL Server, MySQL, and others, and also has a ready-made XML file for Liquibase. SQL scripts Also, QRTZ tables can be automatically created when launching the application by specifying the property. spring.quartz.jdbc.initialize-schema=always For simplicity, we will use the second method and the H2 database. Let's configure a data source, use JDBCJobStore and create QRTZ tables in application.properties: spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always For these settings to be taken into account, the Scheduler must be created as a Spring bean: package quartzdemo; import org.quartz.*; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import quartzdemo.jobs.SimpleJob; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; @SpringBootApplication @EnableScheduling public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); } @Bean() public Scheduler scheduler(SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.start(); return scheduler; } @Bean public CommandLineRunner run(Scheduler scheduler) { return (String[] args) -> { JobDetail job = JobBuilder.newJob(SimpleJob.class) .usingJobData("param", "value") // add a parameter .build(); Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5) .atZone(ZoneId.systemDefault()).toInstant()); Trigger trigger = TriggerBuilder.newTrigger() .startAt(afterFiveSeconds) .build(); scheduler.scheduleJob(job, trigger); }; } } Thread pool configuration Quartz runs each task in a separate thread, and you can configure a thread pool for schedulers. It should also be noted that by default, tasks launched via the annotation and directly via Quartz are launched in different thread pools. We can make sure of this: @Scheduled PeriodicTask.java @Component public class PeriodicTask { @Scheduled(cron = "${cron-string}") public void everyFiveSeconds() { System.out.println(MessageFormat.format("Periodic task: {0}; Thread: {1}", new Date().toString(), Thread.currentThread().getName())); } } SimpleJob.java public class SimpleJob implements Job { @Override public void execute(JobExecutionContext context) { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String param = dataMap.getString("param"); System.out.println(MessageFormat.format("Job: {0}; Param: {1}; Thread: {2}", getClass(), param, Thread.currentThread().getName())); } } the output will be: Periodic task: Thu Jul 07 19:22:45 EDT 2022; Thread: scheduling-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: quartzScheduler_Worker-1 Periodic task: Thu Jul 07 19:22:50 EDT 2022; Thread: scheduling-1 Periodic task: Thu Jul 07 19:22:55 EDT 2022; Thread: scheduling-1 Periodic task: Thu Jul 07 19:23:00 EDT 2022; Thread: scheduling-1 The thread pool for tasks contain only one thread. @Scheduled Let's change the scheduler settings for @Scheduled tasks: package quartzdemo; 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 SchedulingConfiguration implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(10); threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-"); threadPoolTaskScheduler.initialize(); taskRegistrar.setTaskScheduler(threadPoolTaskScheduler); } } the output will now be like this: Periodic task: Thu Jul 07 19:44:10 EDT 2022; Thread: my-scheduled-task-pool-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: quartzScheduler_Worker-1 Periodic task: Thu Jul 07 19:44:15 EDT 2022; Thread: my-scheduled-task-pool-1 Periodic task: Thu Jul 07 19:44:20 EDT 2022; Thread: my-scheduled-task-pool-2 As you can see, these settings affected only the tasks set using annotations. Now let's change the settings of the scheduler that we work with Quartz directly. This can be done in two ways: through the properties file or by creating a bean . SchedulerFactoryBeanCustomizer Let's use the first method. If we hadn't initialized Quartz via Spring, we would have to register properties in the quartz.properties file. In our case, we need to register properties in application.properties, adding the prefix to them. spring.quartz.properties. application.properties spring.quartz.properties.org.quartz.threadPool.threadNamePrefix=my-scheduler_Worker spring.quartz.properties.org.quartz.threadPool.threadCount=25 Let's launch the application. Now the output will be like this: Periodic task: Sat Jul 23 10:45:55 MSK 2022; Thread: my-scheduled-task-pool-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: my-scheduler_Worker-1 Now the thread in which the task is started is called . my-scheduler_Worker-1 Multiple schedulers If you need to create several schedulers with different parameters, you must define several . Let's look at an example. SchedulerFactoryBeans package quartzdemo; import quartzdemo.jobs.SimpleJob; import org.quartz.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import javax.sql.DataSource; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; import java.util.Properties; @SpringBootApplication @EnableScheduling public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); } @Bean("customSchedulerFactoryBean1") public SchedulerFactoryBean customSchedulerFactoryBean1(DataSource dataSource) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); Properties properties = new Properties(); properties.setProperty("org.quartz.threadPool.threadNamePrefix", "my-custom-scheduler1_Worker"); factory.setQuartzProperties(properties); factory.setDataSource(dataSource); return factory; } @Bean("customSchedulerFactoryBean2") public SchedulerFactoryBean customSchedulerFactoryBean2(DataSource dataSource) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); Properties properties = new Properties(); properties.setProperty("org.quartz.threadPool.threadNamePrefix", "my-custom-scheduler2_Worker"); factory.setQuartzProperties(properties); factory.setDataSource(dataSource); return factory; } @Bean("customScheduler1") public Scheduler customScheduler1(@Qualifier("customSchedulerFactoryBean1") SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.start(); return scheduler; } @Bean("customScheduler2") public Scheduler customScheduler2(@Qualifier("customSchedulerFactoryBean2") SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.start(); return scheduler; } @Bean public CommandLineRunner run(@Qualifier("customScheduler1") Scheduler customScheduler1, @Qualifier("customScheduler2") Scheduler customScheduler2) { return (String[] args) -> { Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5).atZone(ZoneId.systemDefault()).toInstant()); JobDetail jobDetail1 = JobBuilder.newJob(SimpleJob.class).usingJobData("param", "value1").build(); Trigger trigger1 = TriggerBuilder.newTrigger().startAt(afterFiveSeconds).build(); customScheduler1.scheduleJob(jobDetail1, trigger1); JobDetail jobDetail2 = JobBuilder.newJob(SimpleJob.class).usingJobData("param", "value2").build(); Trigger trigger2 = TriggerBuilder.newTrigger().startAt(afterFiveSeconds).build(); customScheduler2.scheduleJob(jobDetail2, trigger2); }; } } Output: Job: class quartzdemo.jobs.SimpleJob; Param: value2; Thread: my-custom-scheduler2_Worker-1 Job: class quartzdemo.jobs.SimpleJob; Param: value1; Thread: my-custom-scheduler1_Worker-1 The full source code is available over on . GitHub Conclusion Quartz is a powerful framework for automating scheduled tasks. It can be used both with the help of simple and intuitive Spring annotations and with fine customization and tuning, which provides a solution to complex problems.