1. Overview

In this tutorial we’ll build a simple Scheduler in Spring with Quartz.

We’ll begin with a simple goal in mind – to easily configure a new scheduled job.

1.1. Key Components of the Quartz API

Quartz has a modular architecture. It consists of several basic components that can be combined as required. In this tutorial, we’ll focus on the ones that are common to every job: Job, JobDetail, Trigger and Scheduler.

Although we will use Spring to manage the application, each individual component can configured in two ways: the Quartz way or the Spring way (using its convenience classes).

We will cover both as far as possible for the sake of completeness, but either may be adopted. Let’s start building, one component at a time.

2. Job and JobDetail

2.1. Job

The API provides a Job interface having just one method – execute. It must be implemented by the class that contains the actual work to be done, i.e. the task. When a job’s trigger fires, the scheduler invokes the execute method, passing it a JobExecutionContext object.

The JobExecutionContext provides the job instance with information about its runtime environment, including a handle to the scheduler, a handle to the trigger, and the job’s JobDetail object.

In this quick example – the job delegates the task to a service class: 

1
2
3
4
5
6
7
8
9
10
@Component
public class SampleJob implements Job {
 
    @Autowired
    private SampleJobService jobService;
 
    public void execute(JobExecutionContext context) throws JobExecutionException {
        jobService.executeSampleJob();
    }
}

2.2. JobDetail

While the job is the workhorse, Quartz does not store an actual instance of the job class. Instead, we can define an instance of the Job using the JobDetail class. The job’s class must be provided to the JobDetail so that it knows the type of the job to be executed.

2.3. Quartz JobBuilder

The Quartz JobBuilder provides a builder-style API for constructing JobDetail entities.

1
2
3
4
5
6
7
8
@Bean
public JobDetail jobDetail() {
    return JobBuilder.newJob().ofType(SampleJob.class)
      .storeDurably()
      .withIdentity("Qrtz_Job_Detail"
      .withDescription("Invoke Sample Job service...")
      .build();
}

2.4. Spring JobDetailFactoryBean

Spring’s JobDetailFactoryBean provides bean-style usage for configuring JobDetail instances. It uses the Spring bean name as the job name, if not otherwise specified:

1
2
3
4
5
6
7
8
@Bean
public JobDetailFactoryBean jobDetail() {
    JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
    jobDetailFactory.setJobClass(SampleJob.class);
    jobDetailFactory.setDescription("Invoke Sample Job service...");
    jobDetailFactory.setDurability(true);
    return jobDetailFactory;
}

A new instance of JobDetail is created for every execution of the job. The JobDetail object conveys the detailed properties of the job. Once the execution is completed, references to the instance are dropped.

3. Trigger

A Trigger is the mechanism to schedule a Job, i.e. a Trigger instance “fires” the execution of a job. There’s a clear separation of responsibilities between the Job (notion of task) and Trigger (scheduling mechanism).

In addition to Job, the trigger also needs a type that can be chosen based on the scheduling requirements.

Let’s say, we want to schedule our task to execute once every hour, indefinitely – we can use Quartz’s TriggerBuilder or Spring’s SimpleTriggerFactoryBean to do so.

3.1. Quartz TriggerBuilder

TriggerBuilder is a builder-style API for constructing the Trigger entity:

1
2
3
4
5
6
7
8
@Bean
public Trigger trigger(JobDetail job) {
    return TriggerBuilder.newTrigger().forJob(job)
      .withIdentity("Qrtz_Trigger")
      .withDescription("Sample trigger")
      .withSchedule(simpleSchedule().repeatForever().withIntervalInHours(1))
      .build();
}

3.2. Spring SimpleTriggerFactoryBean

SimpleTriggerFactoryBean provides bean-style usage for configuring SimpleTrigger. It uses the Spring bean name as the trigger name and defaults to indefinite repetition, if not otherwise specified:

1
2
3
4
5
6
7
8
@Bean
public SimpleTriggerFactoryBean trigger(JobDetail job) {
    SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
    trigger.setJobDetail(job);
    trigger.setRepeatInterval(3600000);
    trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
    return trigger;
}

4. Configuring the JobStore

JobStore provides the storage mechanism for the Job and Trigger, and is responsible for maintaining all the data relevant to the job scheduler. The API supports both in-memory and persistent stores.

For example purposes, we will use the in-memory RAMJobStore which offers blazing-fast performance and simple configuration via quartz.properties.

1
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

The obvious drawback of the RAMJobStore is that it is volatile in nature. All the scheduling information is lost between shutdowns. If job definitions and schedules must be kept between shutdowns, the persistent JDBCJobStore must be used instead.

5. Scheduler

The Scheduler interface is the main API for interfacing with the job scheduler.

Scheduler can be instantiated with a SchedulerFactory. Once created, Jobs and Triggers can be registered with it. Initially, the Scheduler is in “stand-by” mode, and its start method must be invoked to start the threads that fire the execution of jobs.

5.1. Quartz StdSchedulerFactory

By simply invoking the getScheduler method on the StdSchedulerFactory, we can instantiate the Scheduler, initialize it (with the configured JobStore and ThreadPool) and return a handle to its API:

1
2
3
4
5
6
7
8
9
10
11
12
@Bean
public Scheduler scheduler(Trigger trigger, JobDetail job) {
    StdSchedulerFactory factory = new StdSchedulerFactory();
    factory.initialize(new ClassPathResource("quartz.properties").getInputStream());
 
    Scheduler scheduler = factory.getScheduler();
    scheduler.setJobFactory(springBeanJobFactory());
    scheduler.scheduleJob(job, trigger);
 
    scheduler.start();
    return scheduler;
}

5.2. Spring SchedulerFactoryBean

Spring’s SchedulerFactoryBean provides bean-style usage for configuring a Scheduler, manages its life-cycle within the application context, and exposes the Scheduler as a bean for dependency injection:

1
2
3
4
5
6
7
8
9
10
@Bean
public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job) {
    SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
    schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
 
    schedulerFactory.setJobFactory(springBeanJobFactory());
    schedulerFactory.setJobDetails(job);
    schedulerFactory.setTriggers(trigger);
    return schedulerFactory;
}

5.3 Configuring SpringBeanJobFactory

The SpringBeanJobFactory provides support for injecting the scheduler context, job data map, and trigger data entries as properties into the job bean while creating an instance.

However, it lacks support for injecting bean references from the application context. Thanks to the author of this blog post, we can add auto-wiring support to SpringBeanJobFactory like so:

1
2
3
4
5
6
@Bean
public SpringBeanJobFactory springBeanJobFactory() {
    AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
    jobFactory.setApplicationContext(applicationContext);
    return jobFactory;
}

6. Conclusion

That’s all. We have just built our first basic scheduler using the Quartz API as well as Spring’s convenience classes.

The key takeaway from this tutorial is that we were able to configure a job with just a few lines of code and without using any XML-based configuration.

The complete source code for the example is available in this github project. It is a Maven project which can be imported and run as-is. The default setting uses Spring’s convenience classes, which can be easily switched to Quartz API with a run-time parameter (refer to the README.md in the repository).