Scheduling

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This Backend Development section is a practical guide, focused on Spring Boot services, to running work on a clock: refresh a cache every few minutes, send reminders every night, create a reminder for one member when a loan is made and cancel it when the book comes back, produce a report that must not run twice and must survive a crash. It complements the SpringBoot Reference, whose Scheduling & ShedLock page documents standard scheduling, and it compares that standard scheduling with the advanced features of Quartz Scheduler: persistent and clustered schedules, misfire policies, calendars, runtime job management and recovery after a crash.

The section documents Quartz 2.5.2 (released 2025-12-01; Java 11 or later, Jakarta namespace; there is no 3.x) on Spring Boot 4.1.1, which manages that version, and Spring Framework 7.0.9, whose spring-context-support provides SchedulerFactoryBean, QuartzJobBean and LocalDataSourceJobStore, with Java 21. Standard scheduling is compared with ShedLock 7.10.1 (Java 17 or later). Where Spring Boot 3.5 differs, the pages show a short "Spring Boot 3.5" delta: Boot 3.5.16 manages the same Quartz 2.5.2, so only the package of the Boot Quartz types (org.springframework.boot.autoconfigure.quartz instead of org.springframework.boot.quartz.autoconfigure) and Spring Framework 6.2.19 differ. The versions were verified on 5 October 2026, and each page states what it measured: behaviours were verified in a test project, against real databases in Docker where a database page says so.

What scheduling means for a backend service

A web request starts when a client sends it; scheduled work starts when the clock says so. That difference creates the problems this section is about:

  • Nobody is waiting. A failed nightly job is noticed late, so jobs must log, expose their state and be safe to run again.

  • The service has more than one copy. Three replicas behind a load balancer would send every reminder three times unless something makes exactly one of them run each job.

  • The clock does not stop for deployments. A restart over a fire time either loses the run or runs it late; the right behaviour is a decision (a misfire policy), not an accident.

  • Some schedules are data. "Remind this member in 14 days" is created at run time, must survive a restart and may be deleted before it fires.

  • Crashes happen halfway. A report that took five minutes and died at minute four has to be finished by somebody.

Spring’s @Scheduled solves the first problem class for the simple case and is the right default. Quartz solves the rest and costs a job store. The section helps you tell which jobs are which.

This section is for developers who:

  • use @Scheduled today, possibly with ShedLock, and need to know when to move to Quartz and how

  • are adding Quartz to a Spring Boot 4 (or 3.5) service and need the starter, the configuration and the database set-up that actually work

  • have to run Quartz on a particular database (PostgreSQL, MySQL, MariaDB, Oracle, SQL Server, H2 or another SQL engine) or want it with MongoDB or Redis

  • want to test scheduled jobs, observe them in production and run them in a cluster

The Bookshelf scenario

Every example builds on Bookshelf, the domain used by the Resilience, FastAPI, NestJS, Django, Next.js and Kubernetes sections of this site, so readers can compare stacks. Here it is a Spring Boot 4.1 bookshelf-service (Java 21) with five kinds of scheduled work, each of which motivates a feature:

A map of five rows. The service bookshelf-service, Spring Boot 4.1 and Java 21, has five scheduled jobs. Job 1, the catalogue cache refresh every 5 minutes, uses at-Scheduled with fixedDelay, because standard scheduling is enough and each node refreshes its own cache. Job 2, the nightly overdue-loan reminders at 02:00 Europe/Madrid on business days, uses at-Scheduled with a cron expression and ShedLock first, then a Quartz CronTrigger with a holiday Calendar and a misfire policy. Job 3, the per-member return reminders created when a loan is made and deleted when the book returns, uses Quartz with a one-shot SimpleTrigger per loan, a JobDataMap with memberId and loanId and a JDBC job store, because the schedule is data that changes at run time. Job 4, the weekly statistics report on Monday at 06:00 that must never overlap itself, uses a three-node Quartz cluster with DisallowConcurrentExecution, requestsRecovery and an idempotent job. Job 5, the monthly retention cleanup on the last business day, uses a Quartz cron with L and LW and a Calendar of holidays, and the job launches a Spring Batch job.
Figure 1. The five jobs of bookshelf-service and the mechanism each one uses
  • Cache refresh of the catalogue every 5 minutes. Standard @Scheduled(fixedDelay) is enough.

  • Nightly overdue-loan reminders at 02:00 Europe/Madrid on business days. @Scheduled(cron) with ShedLock first, then a Quartz CronTrigger with a holiday Calendar and a misfire policy.

  • Per-member "return your book" reminders, created at run time when a loan is made and cancelled when the book is returned. This needs Quartz: dynamic one-shot SimpleTrigger`s, a `JobDataMap and persistence across restarts.

  • Weekly statistics report, long running, which must not overlap itself and must recover after a crash: @DisallowConcurrentExecution and requestsRecovery, in a three-node cluster.

  • Monthly retention cleanup on the last business day: L and LW in cron and a Calendar; it launches a Spring Batch job (linked).

The reference database is PostgreSQL; every database page re-runs the same jobs on its own database so the pages can be compared. The comparison page, Standard Scheduling vs Quartz, migrates the weekly report from @Scheduled to Quartz.

The final layout

The classes the pages build up to, in one Maven module. Each piece is shown on the page named beside it:

bookshelf-service
├── pom.xml                                   spring-boot-starter-quartz, -jdbc, -actuator, the JDBC driver
└── src
    ├── main
    │   ├── java/bookshelf
    │   │   ├── BookshelfApplication.java
    │   │   ├── ScheduleProperties.java       record for bookshelf.schedule.*      (Spring Boot Configuration)
    │   │   ├── BookshelfSchedule.java        JobDetail and Trigger beans          (Getting Started, Spring Boot Configuration)
    │   │   ├── CacheRefreshJob.java          job 1, or @Scheduled in the standard version
    │   │   ├── OverdueLoanRemindersJob.java  job 2, a QuartzJobBean                (Getting Started)
    │   │   ├── MemberReminderJob.java        job 3, created through the Scheduler  (Dynamic Scheduling)
    │   │   ├── ReminderController.java       REST API that creates and cancels reminders
    │   │   ├── WeeklyStatisticsJob.java      job 4, DisallowConcurrentExecution and requestsRecovery  (Clustering)
    │   │   ├── RetentionCleanupJob.java      job 5, launches a Spring Batch job    (Jobs and Job Data)
    │   │   └── JobAuditListener.java         a global JobListener                  (Listeners and Plugins)
    │   └── resources
    │       ├── application.yml               shared settings, below
    │       ├── application-dev.yml           memory store, fast schedules
    │       ├── application-prod.yml          PostgreSQL, clustered, schema owned by Flyway
    │       └── db/migration
    │           └── V1__quartz_tables.sql     tables_postgres.sql from the Quartz jar
    └── test
        └── java/bookshelf                    unit tests, memory-store test, Testcontainers cluster test (Testing)

Source: Spring Boot: Quartz Scheduler. The layout is a map of the pages, not one downloadable project: the five jobs and the schedule beans were compiled and run together for Spring Boot Configuration. Individual pages use local property names in their own examples (for example bookshelf.reminders.), while the consolidated configuration below uses bookshelf.schedule..

The application.yml that every environment shares, with the schedule as configuration so that each environment can speed it up:

application.yml: the settings every environment shares
spring:
  application:
    name: bookshelf-service
  quartz:
    scheduler-name: BookshelfScheduler
    wait-for-jobs-to-complete-on-shutdown: true   # honoured with Quartz's own SimpleThreadPool, not with a Spring TaskExecutor
    overwrite-existing-jobs: false

bookshelf:
  schedule:
    zone: Europe/Madrid
    cache-refresh-interval: 5m
    overdue-cron: "0 0 2 ? * MON-FRI"
    weekly-cron: "0 0 6 ? * MON"
    retention-cron: "0 30 3 LW * ?"         # last weekday of the month

management:
  endpoints:
    web:
      exposure:
        include: health,quartz

Source: Spring Boot: Quartz Scheduler and Spring Boot: Actuator Endpoints. The same file, with the dev, test and prod profiles around it, is on Spring Boot Configuration. The shutdown flag makes Quartz wait for running jobs and record their completion only with its own SimpleThreadPool; with a Spring TaskExecutor it is not honoured, and await-termination keeps the JVM alive without the completion being recorded (see Production & Best Practices).

The production profile turns on the JDBC store and clustering:

application-prod.yml: PostgreSQL, clustered, tables created by a migration tool
spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PASSWORD}
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: never
    properties:
      org.quartz.scheduler.instanceId: AUTO
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
      org.quartz.jobStore.isClustered: "true"

Source: Quartz documentation: Configure Clustering with JDBC-JobStore. The credentials are placeholders resolved from the environment, never literal values. The Testcontainers cluster test that exercises this configuration with two schedulers is on Testing Scheduled Jobs.

Reading path

The pages are ordered so that each builds on the previous ones. Read them in this order the first time, then use them as a reference:

  1. Foundations: this page, then the comparison with standard scheduling.

  2. Quartz core concepts: fundamentals first, then the Spring Boot integration, jobs and job data, triggers, cron, misfires, calendars, listeners and plugins.

  3. Job stores and databases: the generic page first, then PostgreSQL (the reference), then your own database page.

  4. Clustering and Spring Boot: clustering, the complete configuration, and runtime scheduling.

  5. Quality and operations: observability, testing, production practice, and migration.

  6. The cheat sheet: the whole section condensed onto one printable page.

Readers who already use @Scheduled can read the SpringBoot Reference page Scheduling & ShedLock and then Standard Scheduling vs Quartz. Readers who have decided on Quartz can go through Quartz Fundamentals, Getting Started with Spring Boot and then the page of their database. The Cheat Sheet (PDF) condenses the section for quick lookup.

mindmap root((Scheduling)) Foundations Landing page Standard scheduling vs Quartz Quartz core Fundamentals Getting started with Spring Boot Jobs and job data Triggers and schedules Cron expressions Misfires Calendars Listeners and plugins Job stores and databases Job stores and persistence PostgreSQL MySQL and MariaDB Oracle SQL Server H2 and other SQL MongoDB Redis Clustering and Spring Boot Clustering Spring Boot configuration Dynamic scheduling Quality and operations Observability Testing Production and best practices Migration and what changed Cheat sheet
Figure 2. The section at a glance

Source: the pages of this section.

What’s covered

Foundations

  • Scheduling (this page): the scenario, the baseline and the bibliography.

  • Standard Scheduling vs Quartz: a recap of @Scheduled and ShedLock, what they cannot do, a feature-by-feature table, cron dialects, a node crash under each, a decision flowchart, a migration walkthrough and the same idea in other stacks.

Quartz core concepts

  • Quartz Fundamentals: what Quartz is, the 2.4 and 2.5 lines, Scheduler, JobDetail and Trigger, the fluent DSL, the scheduler lifecycle and a plain-Java first example.

  • Getting Started with Spring Boot: the starter, what QuartzAutoConfiguration does, QuartzJobBean, dependency injection into jobs and a first durable job with a cron trigger.

  • Jobs and Job Data: the job instance lifecycle, JobDataMap and its merged map, concurrency and persistence annotations, durability, recovery, JobExecutionException, interruption, idempotent jobs and launching Spring Batch.

  • Triggers & Schedules: common trigger attributes, the four trigger types, choosing one, cookbook recipes and the per-member one-shot reminder.

  • Cron Expressions: the Quartz grammar, special characters, the CronExpression API, time zones and DST, and the translation table to Spring and Unix.

  • Misfires: what a misfire is, misfireThreshold, every instruction per trigger type and choosing a policy per job.

  • Calendars: the calendars that ship with Quartz, chaining and registering them, and a business-day calendar from configuration.

  • Listeners and Plugins: job, trigger and scheduler listeners, matchers, registering them in Spring Boot, and the plugins.

Job stores and databases

Clustering and Spring Boot integration

  • Clustering: row locks, check-ins, fail-over and recovery, the clock requirement, tuning, scaling limits and Kubernetes.

  • Spring Boot Configuration: every spring.quartz.* property with its default, raw Quartz properties, customizers, thread pools, profiles and the complete Bookshelf configuration.

  • Dynamic Scheduling & Management: the injected Scheduler, a reminders REST API, securing it and transactional consistency.

Quality and operations

  • Observability: the Actuator quartz endpoint, scheduledtasks, listener-based metrics and health, JMX, logs and alerting.

  • Testing Scheduled Jobs: unit tests, trigger tests, @SpringBootTest, Testcontainers and a two-scheduler cluster test.

  • Production & Best Practices: the official best-practices page item by item, graceful shutdown, pool sizing, maintenance and an anti-patterns table.

  • Migration and What’s Changed: Quartz 2.3 to 2.5, Boot 3.5 to 4.x, from @Scheduled and ShedLock, between job stores, and the table of places where the official documentation is stale.

Cheat sheet

Where related material lives elsewhere on this site

The section links to existing pages instead of repeating them.

Topic Where

Standard scheduling: @Scheduled, TaskScheduler, ShedLock

Scheduling & ShedLock (stays in the SpringBoot Reference; this section extends it)

The SpringBoot Reference and its cheat sheet

SpringBoot Reference and its cheat sheet

Launching a Spring Batch job from Quartz

Running a Job, Spring Batch Reference and Spring Batch: Getting Started

The same idea in Quarkus

Scheduling and Mail and Quarkus vs. Spring Boot

A Kubernetes CronJob, the out-of-process alternative

Jobs and CronJobs

The same idea in NestJS, Django (Celery beat) and ASP.NET Core (BackgroundService)

Events, Scheduling & Queues, Celery Integration and Hosting, Servers, and Environments

SQL, DDL and transactions behind the QRTZ_ tables

SQL, Spring Data JPA and Transaction Isolation & Locking

Managing the Quartz schema with migrations

Schema Evolution and Choosing a Migration Tool

MongoDB, transactions and replication, and Spring Data MongoDB

MongoDB, Spring Data MongoDB and Mongock: Getting Started

Redis and distributed locks

Redis and Redis: Use Cases and Patterns

Base Actuator, Micrometer and logging setup

Metrics & Observability and Logging

JUnit, Mockito and Testcontainers

Unit & Integration Testing and Spring Boot Integration Tests with Testcontainers

Virtual threads and the executors behind Spring’s scheduler

Concurrency Alternatives

Configuration and profiles, and securing the management API

Configuration & Profiles and Spring Security

Retrying a flaky call inside a job (not the same as Quartz’s refireImmediately)

Resilience

Bibliography

Every source used to write this section, each linked to its official documentation. Individual pages list the subset they derive from under their own == References heading. Where the official Quartz documentation is stale, the pages follow the Quartz 2.5.2 source and Javadoc and link both; the places where they differ are collected on Migration and What’s Changed.

Official Quartz project resources

Databases and drivers

Each database page links its own subset.

Community job stores

Official repositories of those projects; both are flagged as community or unmaintained in the text, and neither worked on Quartz 2.5.x in the tests of the database pages.