Persisting Quartz Scheduler Jobs in a Database
When building Spring web applications, recurring tasks such as sending reminders, generating reports, or processing background data are a common requirement. Quartz Scheduler is one of the most reliable and flexible frameworks for handling such jobs. However, by default, Quartz stores its schedules in memory, which means all job data is lost once the application stops or restarts. In this article, we will explore how to persist Quartz jobs in a database, ensuring they survive restarts and maintain reliability and continuity in production environments.
1. Why Persist Quartz Scheduler?
In a production environment, application restarts are common due to maintenance, updates, or unexpected crashes. By default, Quartz stores scheduled jobs in memory, meaning all schedules are lost once the application shuts down.
Persisting jobs in a database ensures:
- Jobs and triggers survive application restarts.
- The scheduler resumes from its last known state.
- Multiple application nodes can share the same scheduler in clustered environments.
This is essential for tasks that must run on precise schedules, such as daily financial reports, payroll processing, or sending reminders.
2. Maven Dependencies
To use Quartz Scheduler with Spring Boot and JDBC persistence, we need to include the appropriate dependencies in our Maven project.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Here, we use spring-boot-starter-quartz to integrate Quartz into our application. For persistence, Quartz will use JDBC, so we include an in-memory H2 database for demonstration.
Configuration (application.yml)
Next, we configure Quartz to use the database instead of memory for persistence. Quartz can be configured using either application.yml or application.properties. Below, we’ll configure Quartz to use JDBC persistence, connect to an H2 database, and manage schema initialisation.
A recommended strategy is to let Spring Boot generate the Quartz schema during the initial setup and subsequently disable schema initialization. Doing so preserves job data across application restarts and prevents Quartz from dropping or reinitializing its tables once jobs have been persisted.
# Datasource configuration spring.datasource.url=jdbc:h2:file:~/demodb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.hibernate.ddl-auto=create # Quartz configuration spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always
In this configuration, we use H2 as the datasource for demonstration purposes, while Quartz is instructed to rely on JDBC persistence through the property spring.quartz.job-store-type=jdbc. Additionally, the setting spring.quartz.jdbc.initialize-schema=always ensures that Spring Boot automatically creates the required Quartz schema when the application starts.
However, in a production environment, you typically run the schema creation script only once (either manually or during the initial deployment). After that, you should change:
spring.quartz.jdbc.initialize-schema=never
This prevents Spring Boot from re-initialising the schema on each startup, ensuring that persisted job data is not accidentally dropped.
3. Creating a Quartz Job
A Quartz job is a Java class implementing the Job interface. Below is a simple job that logs a message.
public class EmailJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(EmailJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
logger.info("Executing Email Job at {}", context.getFireTime());
}
}
This job will be scheduled by Quartz and executed according to the configured trigger.
Scheduling the Job with a Configuration Class
We now create a configuration class to register our job and trigger.
@Configuration
public class QuartzSchedulerConfig {
@Bean
public JobDetail emailJobDetail() {
return JobBuilder.newJob(EmailJob.class)
.withIdentity("emailJob", "emailGroup")
.storeDurably()
.build();
}
@Bean
public Trigger emailJobTrigger(JobDetail emailJobDetail) {
return TriggerBuilder.newTrigger()
.forJob(emailJobDetail)
.withIdentity("emailTrigger", "emailGroup")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(30)
.repeatForever())
.build();
}
}
In this configuration, the emailJobDetail() method registers our EmailJob as a durable job, ensuring it remains available even without an active trigger. The emailJobTrigger() method then defines a trigger that executes the job every 30 seconds. Since JDBC persistence is enabled, Quartz saves both the job and its trigger in the database, allowing them to survive application restarts and ensuring reliable scheduling.
4. Preserving and Reloading Jobs on Restart
One of the main benefits of persisting Quartz jobs in a database is that scheduled tasks are not lost when the application shuts down or restarts. In a typical in-memory configuration, all job and trigger details vanish at shutdown, requiring manual re-registration. With JDBC persistence, Quartz automatically re-initialises jobs from the database as soon as the application comes back online.
For example, in our configuration, the EmailJob and its trigger are stored in the QRTZ_JOB_DETAILS and QRTZ_TRIGGERS tables. When the application restarts, Quartz queries these tables, reloads the job details, and resumes execution based on the existing schedule. This means that if your job was supposed to fire every 30 seconds, it will continue from the last known state rather than starting over.
This behaviour is particularly valuable in production environments, where missing even a single execution (such as a daily financial report or a scheduled notification) can have a significant impact. By persisting jobs and letting Quartz re-initialise them on restart, you ensure reliability, consistency, and continuity of scheduled tasks across the application lifecycle.
Verifying Re-Initialization with a Unit Test
We can write a simple integration test using Spring Boot Test to confirm that Quartz reloads jobs after a restart.
@SpringBootTest
class QuartzPersistenceTest {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Scheduler scheduler;
@Test
void givenEmailJob_whenSchedulerRestart_thenJobAndTriggerReloadedFromDatabase() throws Exception {
// Verify the job and trigger exist in the running scheduler
JobKey jobKey = new JobKey("emailJob", "emailGroup");
TriggerKey triggerKey = new TriggerKey("emailTrigger", "emailGroup");
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
assertNotNull(jobDetail, "EmailJob should exist in the running scheduler");
Trigger trigger = scheduler.getTrigger(triggerKey);
assertNotNull(trigger, "EmailTrigger should exist in the running scheduler");
// Stop and restart the scheduler
scheduler.standby();
Scheduler restartedScheduler = applicationContext.getBean(Scheduler.class);
restartedScheduler.start();
// The job and trigger should be reloaded from the database
assertTrue(restartedScheduler.isStarted(), "Scheduler should be running after restart");
JobDetail reloadedJob = restartedScheduler.getJobDetail(jobKey);
assertNotNull(reloadedJob, "EmailJob should be reloaded from the database after restart");
Trigger reloadedTrigger = restartedScheduler.getTrigger(triggerKey);
assertNotNull(reloadedTrigger, "EmailTrigger should be reloaded from the database after restart");
}
}
The process begins by verifying that the EmailJob and its associated trigger, EmailTrigger, exist in the currently running scheduler. This step ensures that the job is properly registered and active before simulating a restart.
Next, the scheduler is stopped using the standby() method. A new scheduler instance is then retrieved from the Spring ApplicationContext, effectively simulating a restart, and the scheduler is started again.
Finally, we confirm that both the job and its trigger have been successfully reloaded from the database. This outcome proves that Quartz’s persistence mechanism is functioning as intended, maintaining job definitions across restarts.
5. Conclusion
In this article, we showed how to persist Quartz Scheduler jobs in a Spring Boot application using JDBC. We configured Quartz to store job and trigger details in a database, created a scheduled job, and verified that tasks are automatically reloaded after restarts. This approach ensures reliability and continuity of scheduled tasks in production environments.
6. Download the Source Code
This article covered how to persist Quartz Scheduler jobs in a database with Java.
You can download the full source code of this example here: Java Quartz scheduler persist database

