Mastering Quartz Scheduler in Spring Boot: From Basics to Advanced Persistence
This tutorial walks through Quartz’s core concepts, basic and advanced usage in Spring Boot—including job, trigger, and scheduler setup, multiple triggers, bean injection, and persistent storage—complete with code examples and diagrams for clear understanding.
The author researched Quartz for scheduled tasks and provides a complete tutorial with code available on GitHub.
Contents include an introduction to Quartz, basic usage, and advanced topics such as bean injection and persistence.
1. Getting to Know Quartz
Quartz is ideal for non‑distributed tasks that need dynamic management (start, pause, resume, stop, modify schedule).
Quartz consists of three main components:
Job : the work to be executed.
Trigger : defines when and how often the job runs.
Scheduler : assembles jobs and triggers and executes them.
An analogy compares Job to a product, Trigger to a production line, and Scheduler to a workshop manager.
2. Basic Usage of Quartz
Using Spring Boot, add the spring-boot-starter-quartz dependency, create a Job class, and configure Scheduler and Trigger.
2.1 Interval‑Based Task
Use SimpleTrigger to run a task every 2 seconds for 30 seconds.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency> import org.quartz.Job;
import org.quartz.JobExecutionContext;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class SimpleJob implements Job {
@Override
public void execute(JobExecutionContext context) {
System.out.println(Thread.currentThread().getName() + "--" +
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
}
} import com.quartz.demo.schedule.SimpleJob;
import org.junit.jupiter.api.Test;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
public class SimpleQuartzTest {
@Test
public void simpleTest() throws SchedulerException, InterruptedException {
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger-1", "trigger-group")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
TimeUnit.SECONDS.sleep(30);
scheduler.shutdown();
}
}The log shows the built‑in thread pool of 10 threads, confirming the Scheduler’s thread pool.
2.2 Cron‑Expression Task
import com.quartz.demo.schedule.SimpleJob;
import org.junit.jupiter.api.Test;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
public class SimpleQuartzTest {
@Test
public void cronTest() throws SchedulerException, InterruptedException {
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job-1", "job-group")
.build();
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("trigger-1", "trigger-group")
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 *"))
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
TimeUnit.SECONDS.sleep(30);
scheduler.shutdown();
}
}3. Quartz Deep Dive
The following diagram illustrates the relationships among core classes.
JobDetail : wraps the concrete Job class, created via JobBuilder.
Trigger : defines execution rules, created via TriggerBuilder. One‑to‑many relationship with JobDetail.
Scheduler : core component that executes jobs; provides the main Quartz API.
JobDataMap : a map for storing additional data with a JobDetail.
JobStore : persists job and trigger metadata; supports RAMJobStore (in‑memory) and JDBCJobStore (database).
3.1 Job
A Job implements a single execute() method. Use JobBuilder to create a JobDetail and register it with the Scheduler. The combination of name and group uniquely identifies a JobDetail.
3.2 Trigger
Four trigger implementations exist:
SimpleTrigger : interval‑based execution.
CronTrigger : cron‑expression based.
CalendarIntervalTrigger : calendar‑aware intervals.
DailyTimeIntervalTrigger : daily time‑window execution.
Triggers have states such as NONE, NORMAL, PAUSED, COMPLETE, ERROR, BLOCKED. The state diagram is shown below.
3.3 Scheduler
The Scheduler, created by StdSchedulerFactory, is a singleton that provides APIs to start, shutdown, add jobs, pause jobs, and manage triggers.
4. Advanced Usage of Quartz
4.1 Multiple Triggers for a Single Job
A JobDetail can be associated with multiple triggers. Use storeDurably() to keep a JobDetail without a trigger, add it with addJob(), and bind triggers via forJob() or scheduleJob().
import com.quartz.demo.schedule.SimpleJob;
import org.junit.jupiter.api.Test;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
public class MultiQuartzTest {
@Test
public void multiJobTest() throws SchedulerException, InterruptedException {
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1", "job-group")
.storeDurably()
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "trigger-group")
.startNow()
.forJob(jobDetail)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "trigger-group")
.startNow()
.forJob(jobDetail)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
scheduler.addJob(jobDetail, false);
scheduler.scheduleJob(trigger1);
scheduler.scheduleJob(trigger2);
scheduler.start();
TimeUnit.SECONDS.sleep(20);
scheduler.shutdown();
}
}4.2 Injecting Beans into a Job
Two solutions are presented for accessing Spring beans inside a Quartz job.
Solution 1: JobDataMap
@Autowired
private PersonMapper personMapper;
JobDetail jobDetail = JobBuilder.newJob(MajorJob.class)
.withIdentity(jobName, jobGroupName)
.usingJobData("jobName", "QuartzDemo")
.build();
jobDetail.getJobDataMap().put("personMapper", personMapper); public class MajorJob implements Job {
@Override
public void execute(JobExecutionContext ctx) {
JobDataMap map = ctx.getJobDetail().getJobDataMap();
String jobName = map.getString("jobName");
PersonMapper mapper = (PersonMapper) map.get("personMapper");
// use mapper
}
}This approach may cause java.io.NotSerializableException when persisting the JobDataMap.
Solution 2: Static Utility Class
@Component
public class SpringContextJobUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
public static Object getBean(String name) {
return context.getBean(name);
}
} public class MajorJob implements Job {
@Override
public void execute(JobExecutionContext ctx) {
PersonMapper mapper = (PersonMapper) SpringContextJobUtil.getBean("personMapper");
// use mapper
}
}The static utility method is the recommended way.
4.3 Quartz Persistence
Quartz’s JobStore holds metadata such as job name, status, schedule, etc. The default RAMJobStore stores data in memory, which is lost on restart. To persist data, configure JDBCJobStore.
Steps:
Add a connection‑pool dependency (e.g., c3p0).
Create a Spring configuration class that loads quartz.properties and defines SchedulerFactoryBean and Scheduler beans.
Provide a quartz.properties file with thread‑pool settings, job‑store class, data source configuration, and table prefix.
Execute the provided SQL script to create Quartz tables in the target database.
<!-- c3p0 dependency -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency> @Configuration
public class SchedulerConfig {
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean factory = new PropertiesFactoryBean();
factory.setLocation(new ClassPathResource("/quartz.properties"));
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
bean.setQuartzProperties(quartzProperties());
return bean;
}
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
@Bean
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
} # quartz.properties (excerpt)
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=10
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.dataSource=qzDS
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL=jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=UTF-8
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=root
org.quartz.dataSource.qzDS.maxConnections=10 # SQL script to create Quartz tables (MySQL, InnoDB)
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-- (other DROP statements omitted for brevity)
CREATE TABLE QRTZ_JOB_DETAILS (
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
) ENGINE=InnoDB;
-- (other CREATE statements omitted for brevity)With these configurations, Quartz stores jobs and triggers in the database, ensuring durability across application restarts.
End of tutorial.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
