Quartz is a feature-rich open-source Java library used to schedule jobs. The core components of this library are Job, JobDetail, Trigger, and Scheduler. The JobDetail is where the job definition lies. The Trigger contains the instructions about when the job should get executed and Scheduler who runs the Job.
Spring Boot Maven Dependency for Quartz library #
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
The Core Components #
- Job: We create our own jobs by extending the
QuartzJobBeanclass that implementsJobinterface. This interface has one methodexecute(JobExecutionContext context), which is overriden by theQuartzJobBean. This is where the scheduled task runs and the information on theJobDetailandTriggeris retrieved using theJobExecutionContext. TheQuartzJobBeanalso gives us an abstract methodexecuteInternal(JobExecutionContext context). YourJobclass needs to override theexecuteInternal(...)method and should contain the code you want to get processed when the job gets executed.
public class MyJob extends QuartzJobBean {
...
@Override
protected void executeInternal(@NonNull JobExecutionContext context) {
...
}
}
- JobDetail: Defining the Job.
JobDetail jobDetail =
JobBuilder.newJob(MyJob.class)
.withIdentity(jobName)
.storeDurably()
.requestRecovery()
.usingJobData(jobDataMap)
.build();
- Trigger: Defining the schedule upon which the job will be executed.
Trigger jobTrigger =
TriggerBuilder.newTrigger()
.forJob(jobDetail)
.startAt(scheduledAt)
.withIdentity(triggerName)
.build();
- Scheduler: This is the main API for interacting with the Quartz Scheduler. We can get an instance of the
Schedulerfrom theSchedulerFactoryBean.
public class MyJob extends QuartzJobBean {
@Autowired SchedulerFactoryBean schedulerFactoryBean;
...
public void start(...) {
Scheduler timer = schedulerFactoryBean.getScheduler();
timer.start();
...
timer.scheduleJob(jobDetail, jobTrigger);
}
Job Persistence #
Quartz gives us JobStore for storing all the Job, Trigger and Scheduler related data.
There are mostly two types of JobStore:
-
RAMJobStore: This is the simplest and the default
JobStorethat stores all of the data in the RAM. It’s a volatile storage and hence we will lose our data if our program crashes or the system is being restarted. -
JDBCJobStore: This is where the database kicks in. This
JobStorestores data in a database via JDBC API. This is how we configure our Quartz to use theJDBCJobStore, inside ourapplication.propertiesfile.
# Quartz
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.initialize-schema=never
spring.quartz.properties.org.quartz.jobStore.tablePrefix=qrtz_
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
Now Let’s Create a Job on the Fly #
We will create the JobDetail and Trigger, and submit them to the Scheduler so that the Job is registered with the Quartz.
@Component
@Slf4j
@DisallowConcurrentExecution
public class OnTheFlyJob extends QuartzJobBean {
@Autowired SchedulerFactoryBean schedulerFactoryBean;
@Override
protected void executeInternal(@NonNull JobExecutionContext context) {
// This is how we can get the JobDataMap from the JobExecutionContext
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
// Here goes our task logic that we want to get processed
// when the job gets executed
}
// This method can be called from a client code
// who wants to create and schedule the job
public void start(...) {
try {
Scheduler timer = schedulerFactoryBean.getScheduler();
// The Scheduler starts listening to job triggers
// after we start() the Scheduler
timer.start();
// We can use the JobDataMap to add any kind of data
// that we might need during the job execution
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(..., ...);
JobDetail jobDetail =
JobBuilder.newJob(OnTheFlyJob.class)
.withIdentity(getUniqueJobName("onTheFlyJob"))
.storeDurably()
.requestRecovery()
.usingJobData(jobDataMap)
.build();
// storeDurably() persists the job data in the database
// requestRecovery() recovers job execution if it fails in the first place for some reason
Trigger jobTrigger =
TriggerBuilder.newTrigger()
.forJob(jobDetail)
.startAt(scheduledAt)
.withIdentity("onTheFlyJobTrigger")
.build();
// The job is scheduled to start at a specified time.
// Here we submit the job detail and the trigger to the scheduler
timer.scheduleJob(jobDetail, jobTrigger);
return jobDetail;
} catch(ObjectAlreadyExistsException e) {
// We have disallowed concurrent job execution
// so if we try to run a job with the same identity
// Quartz will throw this exception
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
private String getUniqueJobName(String suffix) {
String identifier = ...
// We can make use of random number as our identifier
// to uniquely identify our jobs.
// Or, we can use some relevant ID as our identifier.
return identifier + "-" + suffix;
}
}
That’s how we create a Quartz job dynamically in Spring Boot.