Spring Boot Quartz Scheduler Guide with Examples

Spring Boot is a popular framework that simplifies Java application development, especially for web applications. Quartz Scheduler is one of the most robust and flexible solutions for scheduling tasks. Quartz allows job scheduling with complex time-based execution patterns.

In this article, we’ll explore how to integrate Quartz Scheduler with Spring Boot, both with and without a database. We’ll cover the basics of Quartz, its configuration, and implementation for different use cases.

What is a Quartz Scheduler?

Quartz is a job scheduling library that allows scheduling and executing tasks at specific intervals or times. It supports:

  • Cron-based scheduling (like UNIX cron jobs)
  • Simple intervals (e.g., every 5 seconds)
  • Persistent job storage (in a database)
  • Clustering (for distributed execution)

Setting Up Quartz Scheduler in Spring Boot

To use Quartz in a Spring Boot project, you need to include the following dependency in your pom.xml (for Maven):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

1. Quartz Scheduler Without Database

This approach stores job details in memory rather than in a database. It’s useful for lightweight applications where persistence isn’t necessary.

Step 1: Define a Quartz Job

Quartz Job is a class that implements the Job interface and define the task.

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class SimpleQuartzJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("Executing Quartz Job - {}", System.currentTimeMillis());
    }
}

Step 2: Configure Quartz Scheduler

Since we’re using an in-memory job store, we’ll define the scheduler configuration in application.properties:

spring.quartz.job-store-type=memory
spring.quartz.scheduler.instanceName=SimpleScheduler
spring.quartz.threadPool.threadCount=5

Alternative application.yml

spring:
  quartz:
    job-store-type: memory
    scheduler:
      instance-name: SimpleScheduler
    thread-pool:
      thread-count: 5

Next, we create a configuration class to schedule the job:

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(SimpleQuartzJob.class)
                .withIdentity("simpleJob")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger trigger(JobDetail jobDetail) {
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity("simpleTrigger")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(10)
                        .repeatForever())
                .build();
    }
}

Step 3: Running the Application

Run the Spring Boot application, and you’ll see logs indicating that the job is executing every 10 seconds:

Executing Quartz Job - 1742460110793
Executing Quartz Job - 1742460120792

2. Quartz Scheduler With Database

If you need to persist job details, storing jobs in a database is the way to go. This ensures jobs survive application restarts and supports clustering.

Setting Up PostgreSQL Database Server

Step 1: Configure Database for Quartz

First, add the required database driver to pom.xml:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

Modify application.properties to configure a database-backed job store:

spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

spring.datasource.url=jdbc:postgresql://localhost:5432/quartzdb
spring.datasource.username=admin
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver

spring.quartz.job-store-type=jdbc
spring.quartz.scheduler.instance-name=DBScheduler
spring.quartz.jdbc.initialize-schema=always
spring.quartz.properties.org.quartz.scheduler.instanceName=QuartzScheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.threadPool.threadCount=10
spring.quartz.properties.org.quartz.threadPool.threadPriority=5

Alternative application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: none
      naming:
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
  datasource:
    url: jdbc:postgresql://localhost:5432/quartzdb
    username: admin
    password: password
    driver-class-name: org.postgresql.Driver
  quartz:
    job-store-type: jdbc
    scheduler:
      instance-name: DBScheduler
    jdbc:
      initialize-schema: always
    properties:
      org.quartz.scheduler.instanceName: QuartzScheduler
      org.quartz.scheduler.instanceId: AUTO
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
      org.quartz.jobStore.tablePrefix: QRTZ_
      org.quartz.jobStore.isClustered: true
      org.quartz.threadPool.threadCount: 10
      org.quartz.threadPool.threadPriority: 5

Quartz Configuration Reference

Step 2: Create Database Tables

Quartz Scheduler PostgreSQL Table Creation Script (tables_postgres.sql)

-- --------------------------------------------
-- Table structure for table `QRTZ_CALENDARS`
-- --------------------------------------------
CREATE TABLE QRTZ_CALENDARS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME VARCHAR(200) NOT NULL,
    CALENDAR BYTEA NOT NULL,
    PRIMARY KEY (SCHED_NAME, CALENDAR_NAME)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_CRON_TRIGGERS`
-- --------------------------------------------
CREATE TABLE QRTZ_CRON_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) 
        REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_FIRED_TRIGGERS`
-- --------------------------------------------
CREATE TABLE QRTZ_FIRED_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT NOT NULL,
    SCHED_TIME BIGINT NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200),
    JOB_GROUP VARCHAR(200),
    IS_NONCONCURRENT BOOLEAN,
    REQUESTS_RECOVERY BOOLEAN,
    PRIMARY KEY (SCHED_NAME, ENTRY_ID)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_JOB_DETAILS`
-- --------------------------------------------
CREATE TABLE QRTZ_JOB_DETAILS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250),
    JOB_CLASS_NAME VARCHAR(250) NOT NULL,
    IS_DURABLE BOOLEAN NOT NULL,
    IS_NONCONCURRENT BOOLEAN NOT NULL,
    IS_UPDATE_DATA BOOLEAN NOT NULL,
    REQUESTS_RECOVERY BOOLEAN NOT NULL,
    JOB_DATA BYTEA,
    PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_LOCKS`
-- --------------------------------------------
CREATE TABLE QRTZ_LOCKS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME, LOCK_NAME)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_PAUSED_TRIGGER_GRPS`
-- --------------------------------------------
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_SCHEDULER_STATE`
-- --------------------------------------------
CREATE TABLE QRTZ_SCHEDULER_STATE (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT NOT NULL,
    CHECKIN_INTERVAL BIGINT NOT NULL,
    PRIMARY KEY (SCHED_NAME, INSTANCE_NAME)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_SIMPLE_TRIGGERS`
-- --------------------------------------------
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT NOT NULL,
    REPEAT_INTERVAL BIGINT NOT NULL,
    TIMES_TRIGGERED BIGINT NOT NULL,
    PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) 
        REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_SIMPROP_TRIGGERS`
-- --------------------------------------------
CREATE TABLE QRTZ_SIMPROP_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512),
    STR_PROP_2 VARCHAR(512),
    STR_PROP_3 VARCHAR(512),
    INT_PROP_1 INTEGER,
    INT_PROP_2 INTEGER,
    LONG_PROP_1 BIGINT,
    LONG_PROP_2 BIGINT,
    DEC_PROP_1 NUMERIC(13,4),
    DEC_PROP_2 NUMERIC(13,4),
    BOOL_PROP_1 BOOLEAN,
    BOOL_PROP_2 BOOLEAN,
    PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) 
        REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);

-- --------------------------------------------
-- Table structure for table `QRTZ_TRIGGERS`
-- --------------------------------------------
CREATE TABLE QRTZ_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250),
    NEXT_FIRE_TIME BIGINT,
    PREV_FIRE_TIME BIGINT,
    PRIORITY INTEGER,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT NOT NULL,
    END_TIME BIGINT,
    CALENDAR_NAME VARCHAR(200),
    MISFIRE_INSTR SMALLINT,
    JOB_DATA BYTEA,
    PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) 
        REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP)
);

Explanation of Key Tables

quartz table

View the Table using pgAdmin4

http://localhost:5050/browser/
quartz table

Step 3: Modify the Configuration

Now, modify QuartzConfig to use database storage:

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuartzDBConfig {

    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(DatabaseQuartzJob.class)
                .withIdentity("dbJob")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger trigger(JobDetail jobDetail) {
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity("dbTrigger")
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 * * * ?")) // Runs every minute
                .build();
    }
}

Step 4: Define a Job

Create a Quartz job that stores logs in a database:

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class DatabaseQuartzJob implements Job {
    private static final Logger logger = LoggerFactory.getLogger(DatabaseQuartzJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        logger.info("Executing Database Quartz Job - " + System.currentTimeMillis());
    }
}

Step 5: Run the Application

Start your Spring Boot application, and you will see logs in the database and console:

INFO: Executing Database Quartz Job - 1742461800022
INFO: Executing Database Quartz Job - 1742461860011

Query data from Table QUARTZ_

select * from public.qrtz_scheduler_state
qrtz_scheduler_state
select * from public.qrtz_job_details
qrtz_job_details
select * from public.qrtz_triggers
qrtz_triggers

Comparison: With vs. Without Database

comparison

Conclusion

Quartz Scheduler is a powerful tool for scheduling jobs in Spring Boot applications. Using in-memory storage is ideal for simple, non-persistent tasks, while a database-backed approach is better for persistence, clustering, and enterprise applications.

To choose the best approach, consider your application’s needs:

  • Use in-memory for lightweight, temporary tasks.
  • Use database-backed scheduling for persistent, fault-tolerant jobs.

Leave a Comment

Your email address will not be published. Required fields are marked *