1. Overview
Quartz has a modular architecture consisting of several key components, such as Job, JobDetail, Trigger, and Scheduler, that we can combine as needed. Although we’ll use Spring to manage the application, each component can be configured either the Quartz way or the Spring way using its convenience classes. For completeness, we’ll look at both approaches where appropriate, but we’ll adopt whichever fits best as we go. Now let’s start building, one component at a time.
In this tutorial, we’ll build a simple Scheduler in Spring with Quartz, starting with the straightforward goal of making it easy to configure a new scheduled job.
2. Job and JobDetail
The first step in defining a Quartz job is to create the task itself. We do this by implementing the Job interface and then wrapping it with a JobDetail that provides metadata about the job instance.
2.1. Job
The API provides a Job interface that has 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:
@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 doesn’t 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:
@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:
@Bean
public JobDetailFactoryBean jobDetail() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(SampleJob.class);
jobDetailFactory.setDescription("Invoke Sample Job service...");
jobDetailFactory.setDurability(true);
return jobDetailFactory;
}
Every job execution creates a new instance of JobDetail. The JobDetail object conveys the detailed properties of the job. Once the execution is complete, 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 a Job, the trigger also needs a type, which we can choose based on the scheduling requirements.
Let’s say we want to schedule our task to execute once every hour indefinitely, then 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:
@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:
@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. It’s also responsible for maintaining all the data relevant to the job scheduler. The API supports both in-memory and persistent stores.
4.1. In-Memory JobStore
For our example, we’ll use the in-memory RAMJobStore, which offers blazing-fast performance and simple configuration via quartz.properties:
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
The obvious drawback of the RAMJobStore is that it’s volatile in nature. All the scheduling information is lost between shutdowns. If we need to keep job definitions and schedules between shutdowns, we can use the persistent JDBCJobStore instead.
To enable an in-memory JobStore in Spring, we’ll set this property in our application.properties:
spring.quartz.job-store-type=memory
4.2. JDBC JobStore
There are two types of JDBCJobStore: JobStoreTX and JobStoreCMT. They both do the same job of storing scheduling information in a database.
The difference between the two is how they manage the transactions that commit the data. The JobStoreCMT type requires an application transaction to store data, whereas the JobStoreTX type starts and manages its own transactions.
There are several properties to set for a JDBCJobStore. At a minimum, we must specify the type of JDBCJobStore, the data source, and the database driver class. There are driver classes for most databases, but StdJDBCDelegate covers most cases:
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=quartzDataSource
Setting up a JDBC JobStore in Spring takes a few steps. First, we’ll set the store type in our application.properties:
spring.quartz.job-store-type=jdbc
Then we’ll need to enable auto-configuration and give Spring the data source needed by the Quartz scheduler. The @QuartzDataSource annotation does the hard work in configuring and initializing the Quartz database for us:
@Configuration
@EnableAutoConfiguration
public class SpringQrtzScheduler {
@Bean
@QuartzDataSource
public DataSource quartzDataSource() {
return DataSourceBuilder.create().build();
}
}
5. Scheduler
The Scheduler interface is the main API for interfacing with the job scheduler.
A Scheduler can be instantiated with a SchedulerFactory. Once created, we can register Jobs and Triggers with it. Initially, the Scheduler is in “stand-by” mode, and we must invoke its start method 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:
@Bean
public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory)
throws SchedulerException {
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(job, trigger);
scheduler.start();
return scheduler;
}
5.2. Spring SchedulerFactoryBean
Spring’s SchedulerFactoryBean provides bean-style usage for configuring a Scheduler, managing its life-cycle within the application context, and exposing the Scheduler as a bean for dependency injection:
@Bean
public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) {
SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
schedulerFactory.setJobFactory(springBeanJobFactory());
schedulerFactory.setJobDetails(job);
schedulerFactory.setTriggers(trigger);
schedulerFactory.setDataSource(quartzDataSource);
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:Â
@Bean
public SpringBeanJobFactory springBeanJobFactory() {
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
6. Triggering Quartz Jobs Using Spring Boot Actuator
Starting with Spring Boot 3.5.0, Actuator provides a dedicated Quartz endpoint that allows dynamic interaction with scheduled jobs. This endpoint supports operations such as triggering, pausing, resuming, deleting, and inspecting jobs through a RESTful interface.
In this section, we focus specifically on how to trigger Quartz jobs using Spring Boot Actuator.
6.1. Enabling Actuator for Quartz
To enable the Actuator for Quartz, the first step is to add both the Actuator and Quartz dependencies to your project
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
Once the dependencies are added, the next step is to expose the Quartz endpoint by adding the following configuration to the application.properties file:
management.endpoints.web.exposure.include=quartz
management.endpoint.quartz.enabled=true
After completing these steps, Quartz job management will be accessible via REST endpoints under /actuator/quartz.
6.2. Manually Triggering a Quartz Job
After enabling the Actuator, we can manually trigger a Quartz job at any time, bypassing its scheduled execution.
To trigger a job, Spring Boot Actuator provides the following REST endpoint:
POST /actuator/quartz/jobs/{jobGroup}/{jobName}
For example, to trigger a job named Qrtz_Job_Detail in the DEFAULT group, we run:
curl -X POST "http://localhost:8080/actuator/quartz/jobs/DEFAULT/Qrtz_Job_Detail" \
-H "Content-Type: application/json" \
-d '{"state":"running"}'
The request should contain a JSON payload such as {“state”:”running”}. While the state attribute does not currently affect the job’s execution, the API requires it to process the request properly. Upon a successful call, the response will provide information about the triggered job, including its name, group, implementing class, and the precise timestamp when it was triggered:
{
"group" : "DEFAULT",
"name" : "Qrtz_Job_Detail",
"className" : "org.baeldung.springquartz.basics.scheduler.SampleJob",
"triggerTime" : "2025-05-31T10:36:36.474563900Z"
}
Manually triggering jobs is useful for debugging, as it allows us to quickly execute a job and observe its behavior without waiting for its scheduled time. It’s also ideal for running ad-hoc tasks like maintenance and handling urgent scenarios requiring immediate job execution. By triggering jobs manually, we gain flexibility and control to respond efficiently to real-time events and system needs.
While this section focuses on triggering jobs, it’s worth noting that the Quartz Actuator endpoint also supports other operations such as pausing, resuming, deleting jobs, and retrieving job or group information, providing a complete set of REST endpoints for runtime Quartz job management.
7. Conclusion
In this article, we built our first basic scheduler using the Quartz API, as well as Spring’s convenience classes.
The key takeaway is that we’re able to configure a job with just a few lines of code, without using any XML-based configuration.
The code backing this article is available on GitHub. Once you're
logged in as a Baeldung Pro Member, start learning and coding on the project.
The complete source code for the example is available over on GitHub.