Skip to main content
  1. Articles/

Dynamic Quartz Job Scheduling in Spring Boot

·4 mins
Mayukh Datta
Technical Quartz Spring Boot
Table of Contents

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
#

  1. Job: We create our own jobs by extending the QuartzJobBean class that implements Job interface. This interface has one method execute(JobExecutionContext context), which is overriden by the QuartzJobBean. This is where the scheduled task runs and the information on the JobDetail and Trigger is retrieved using the JobExecutionContext. The QuartzJobBean also gives us an abstract method executeInternal(JobExecutionContext context). Your Job class needs to override the executeInternal(...) 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) {
    ...
  }
}
  1. JobDetail: Defining the Job.
JobDetail jobDetail =
          JobBuilder.newJob(MyJob.class)
              .withIdentity(jobName)
              .storeDurably()
              .requestRecovery()
              .usingJobData(jobDataMap)
              .build();
  1. Trigger: Defining the schedule upon which the job will be executed.
Trigger jobTrigger =
          TriggerBuilder.newTrigger()
              .forJob(jobDetail)
              .startAt(scheduledAt)
              .withIdentity(triggerName)
              .build();
  1. Scheduler: This is the main API for interacting with the Quartz Scheduler. We can get an instance of the Scheduler from the SchedulerFactoryBean.
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:

  1. 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.

  2. 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 the JDBCJobStore, inside our application.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.