Dynamic Quartz Job Scheduling in Spring Boot
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
QuartzJobBean
class that implementsJob
interface. This interface has one methodexecute(JobExecutionContext context)
, which is overriden by theQuartzJobBean
. This is where the scheduled task runs and the information on theJobDetail
andTrigger
is retrieved using theJobExecutionContext
. TheQuartzJobBean
also gives us an abstract methodexecuteInternal(JobExecutionContext context)
. YourJob
class 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
Scheduler
from 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
JobStore
that 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
JobStore
stores data in a database via JDBC API. This is how we configure our Quartz to use theJDBCJobStore
, inside ourapplication.properties
file.
# 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.