Understanding Quartz: The Core of Distributed Task Scheduling

This article introduces Quartz, the Java‑based open‑source scheduler behind many distributed task‑scheduling frameworks, compares its RAMJobStore and JDBCJobStore options, shows step‑by‑step Spring Boot integration with code samples, and explains the internal architecture and lifecycle of the scheduler.

Shepherd Advanced Notes
Shepherd Advanced Notes
Shepherd Advanced Notes
Understanding Quartz: The Core of Distributed Task Scheduling

1. Introduction

Our company recently deployed the distributed task scheduling framework XXL‑JOB, which is built on the open‑source Quartz scheduler. Quartz, written entirely in Java, is a de‑facto standard for Java‑based timed jobs.

1.1 What is Quartz

Quartz is an open‑source job‑scheduling project from the OpenSymphony community. It provides a “task progress manager” that triggers execution of other components at pre‑defined times, offering richer features than java.util.Timer.

Powerful scheduling capabilities supporting many scheduling methods.

Flexible usage with multiple job‑store options.

Built‑in support for distributed and clustered deployments.

1.2 Storage Options

Quartz supports two main JobStore implementations:

RAMJobStore – No external database required, easy to configure, fast execution, but all scheduling data resides in JVM memory and is lost when the application stops; capacity limited by memory.

JDBCJobStore – Persists job data in a relational database (e.g., MySQL), supports clustering, survives server restarts, and can recover failed executions; performance depends on database connectivity.

To enable clustering, JDBCJobStore must be used. The required tables (e.g., QRTZ_JOB_DETAILS, QRTZ_TRIGGERS, QRTZ_CRON_TRIGGERS, etc.) are created from the provided SQL script.

2. Spring Boot Integration Example

Integrating Quartz with Spring Boot is straightforward. The example uses JDBCJobStore and includes the following Maven dependencies:

<!-- Maven dependencies for spring-boot-starter-quartz, spring-boot-starter-jdbc, mysql-connector-java 5.1.48 -->

Data source configuration can share the business database or use a separate one. A multi‑data‑source configuration class is shown, defining beans for the user data source and the Quartz data source.

@Configuration
public class DataSourceConfiguration {
    @Primary
    @Bean(name = "userDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.user")
    public DataSourceProperties userDataSourceProperties() { return new DataSourceProperties(); }

    @Primary
    @Bean(name = "userDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.user.hikari")
    public DataSource userDataSource() { return createHikariDataSource(userDataSourceProperties()); }

    @Bean(name = "quartzDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.quartz")
    public DataSourceProperties quartzDataSourceProperties() { return new DataSourceProperties(); }

    @Bean(name = "quartzDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.quartz.hikari")
    @QuartzDataSource
    public DataSource quartzDataSource() { return createHikariDataSource(quartzDataSourceProperties()); }

    private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
        HikariDataSource ds = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        if (StringUtils.hasText(properties.getName())) { ds.setPoolName(properties.getName()); }
        return ds;
    }
}

Application properties configure the scheduler name, job‑store type, auto‑startup, thread‑pool size, and other Quartz settings:

spring:
  datasource:
    url: jdbc:mysql://10.10.0.10:3306/ptc_job?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
  quartz:
    scheduler-name: clusteredScheduler
    job-store-type: jdbc
    auto-startup: true
    startup-delay: 0
    wait-for-jobs-to-complete-on-shutdown: true
    overwrite-existing-jobs: true
    jdbc:
      initialize-schema: never
    properties:
      org:
        quartz:
          jobStore:
            dataSource: quartzDataSource
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_
            isClustered: true
            clusterCheckinInterval: 1000
            useProperties: false
          threadPool:
            threadCount: 25
            threadPriority: 5
            class: org.quartz.simpl.SimpleThreadPool

A simple job Job1 extends QuartzJobBean and logs execution details. The job is declared with @DisallowConcurrentExecution and injected with a Spring service.

@DisallowConcurrentExecution
public class Job1 extends QuartzJobBean {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private static SimpleDateFormat fullDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private AtomicInteger count = new AtomicInteger();
    @Autowired
    private DemoService demoService;
    private String k1;
    public void setK1(String k1) { this.k1 = k1; }
    @Override
    protected void executeInternal(JobExecutionContext context) {
        logger.info("[job1 executed, time: {}, k1={}, count={}, demoService={}]",
            fullDateFormat.format(new Date()), k1, count.incrementAndGet(), demoService);
    }
}

Job and trigger beans are defined using JobBuilder and TriggerBuilder. A SimpleScheduleBuilder runs the job every 30 seconds; a CronScheduleBuilder example runs it every minute.

@Bean
public JobDetail job1() {
    return JobBuilder.newJob(Job1.class)
        .withIdentity("job1")
        .storeDurably()
        .usingJobData("k1", "v1")
        .build();
}

@Bean
public Trigger simpleJobTrigger() {
    SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
        .withIntervalInSeconds(30)
        .repeatForever();
    return TriggerBuilder.newTrigger()
        .forJob(job1())
        .withIdentity("job1Trigger")
        .withSchedule(scheduleBuilder)
        .build();
}

@Bean
public Trigger cronJobTrigger() {
    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0 0/1 * * * ? *");
    return TriggerBuilder.newTrigger()
        .forJob(job1())
        .withIdentity("job1Trigger")
        .withSchedule(scheduleBuilder)
        .build();
}

Running the application produces log entries such as:

2022-09-20 23:17:33.500  INFO 18982 --- [eduler_Worker-2] : [job1 executed, time: 2022-09-20 23:17:33, k1=v1, count=1, demoService=DemoService@3258ebff]

The logs show that each execution creates a new Quartz job instance while the injected DemoService remains a singleton Spring bean, demonstrating automatic property injection from JobDataMap.

3. Implementation Principles

Quartz uses a Scheduler to manage JobDetail and Trigger objects, storing them in a JobStore (in‑memory or persistent). A dedicated QuartzSchedulerThread continuously polls the JobStore for the next trigger, handing jobs to a thread pool for execution.

Key classes include: QuartzSchedulerThread – Executes triggers registered with the scheduler. ThreadPool – Provides worker threads for job execution. QuartzSchedulerResources – Holds all resources needed to create a QuartzScheduler. SchedulerFactory – Creates scheduler instances (e.g., StdSchedulerFactory). JobStore – Persists jobs and triggers. Scheduler, Trigger, JobDetail, Job – Core scheduling abstractions.

The article walks through three main steps:

Scheduler initialization – Create a SchedulerFactory, obtain a Scheduler, and invoke instantiate() to set up thread pools, JobStore, and other resources.

Registering jobs and triggers – Call scheduler.scheduleJob(jobDetail, trigger), which stores the job/trigger in the JobStore and notifies listeners.

Starting the scheduler – Invoke scheduler.start(), which starts the QuartzSchedulerThread, wakes waiting threads, and begins processing triggers.

The QuartzSchedulerThread.run() method loops indefinitely, fetching the next fire time from the JobStore and dispatching jobs to the thread pool, which is the core mechanism for periodic task execution.

Quartz architecture diagram
Quartz architecture diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaDistributed SchedulingSpring BootQuartzJob SchedulingJDBCJobStore
Shepherd Advanced Notes
Written by

Shepherd Advanced Notes

Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.