DEV Community

Sadiul Hakim
Sadiul Hakim

Posted on

Spring Boot Scheduling & Quartz — Complete Guide

1. What Is Scheduling?

Scheduling means executing a task automatically at specific intervals or times — without manual intervention.
For example:

  • Sending daily email reports at 9 AM.
  • Cleaning up expired sessions every midnight.
  • Syncing data with a remote API every 15 minutes.

Analogy:

Like setting a reminder/alarm in your phone — once set, it just runs at the given time.


2. When To Use Scheduling (And When Not)

Use scheduling when:

  • You need periodic background jobs, like:

    • Cache cleanup
    • Sending notifications
    • Data synchronization
    • Analytics aggregation
  • You can tolerate small delays (non-real-time).

Avoid scheduling when:

  • You need instant reactions to user input → use events or message queues instead.
  • The execution needs high reliability and persistence → better use a job queue system (e.g., Redis Queue, Kafka, etc.).
  • You have distributed systems where the same job might run in multiple nodes simultaneously (unless you use Quartz’s clustering feature).

3. Simple Scheduling in Spring Boot

Spring Boot has built-in scheduling support using @Scheduled.

Step 1 — Enable Scheduling

@SpringBootApplication
@EnableScheduling
public class SchedulerApp {
    public static void main(String[] args) {
        SpringApplication.run(SchedulerApp.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2 — Create a Scheduled Task

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class SimpleScheduler {

    // Runs every 5 seconds
    @Scheduled(fixedRate = 5000)
    public void taskFixedRate() {
        System.out.println("Running every 5 seconds: " + System.currentTimeMillis());
    }

    // Runs at specific cron expression (every minute)
    @Scheduled(cron = "0 * * * * *")
    public void taskCron() {
        System.out.println("Cron task every minute");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3 — Common Parameters

  • fixedRate: runs repeatedly at fixed intervals (starts new task after interval, even if previous not done).
  • fixedDelay: waits until the previous task completes, then waits the delay.
  • cron: full cron syntax for complex schedules.

4. Limitations of Simple Spring Scheduling

While @Scheduled is simple, it’s static and limited:

Limitation Description
Static schedule You must hardcode the schedule in code (@Scheduled annotation).
No persistence If the app restarts, jobs are lost — no history.
No control You cannot pause/resume/modify job runtime.
No clustering Multiple app instances will run the same job.
No job metadata You can’t track last execution, duration, or failures easily.

That’s where Quartz Scheduler comes in.


5. Introducing Quartz Scheduler

What is Quartz?

Quartz is a powerful, open-source job scheduling library designed for enterprise-grade use cases.

Key Features:

  • Persistent jobs (backed by a database)
  • Dynamic scheduling (add/edit/delete jobs at runtime)
  • Cron-based triggers
  • Clustering for distributed systems
  • Job history, monitoring, pausing, resuming
  • Supports concurrency control

Real-world uses:

  • Payment retry jobs
  • Batch data processing
  • Generating scheduled reports

6. Using Quartz with Spring Boot

Step 1 — Add Dependency

In pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Step 2 — Configure Quartz

In application.yml:

spring:
  quartz:
    job-store-type: jdbc   # or memory
    jdbc:
      initialize-schema: always
    properties:
      org:
        quartz:
          scheduler:
            instanceName: MyScheduler
          threadPool:
            threadCount: 5
Enter fullscreen mode Exit fullscreen mode

Use job-store-type: jdbc to persist jobs in DB (Spring Boot will auto-create Quartz tables).


7. Example: Static Job with Quartz

Step 1 — Define a Job

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;

@Component
public class EmailJob implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        System.out.println("Sending email at " + System.currentTimeMillis());
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2 — Register Job and Trigger

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

@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail emailJobDetail() {
        return JobBuilder.newJob(EmailJob.class)
                .withIdentity("emailJob")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger emailJobTrigger() {
        CronScheduleBuilder schedule = CronScheduleBuilder.cronSchedule("0 0/1 * * * ?"); // every 1 min
        return TriggerBuilder.newTrigger()
                .forJob(emailJobDetail())
                .withIdentity("emailTrigger")
                .withSchedule(schedule)
                .build();
    }
}
Enter fullscreen mode Exit fullscreen mode

That’s it — Quartz will automatically start and execute the job.


8. Dynamic Jobs and Triggers (Runtime Creation)

This is where Quartz shines.
You can add, update, or remove jobs dynamically at runtime — for example, from a REST API.

Example — Create Job Dynamically

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DynamicJobService {

    @Autowired
    private Scheduler scheduler;

    public void scheduleJob(String jobName, String group, String cron) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(EmailJob.class)
                .withIdentity(jobName, group)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName + "Trigger", group)
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
    }

    public void deleteJob(String jobName, String group) throws SchedulerException {
        scheduler.deleteJob(new JobKey(jobName, group));
    }

    public void pauseJob(String jobName, String group) throws SchedulerException {
        scheduler.pauseJob(new JobKey(jobName, group));
    }

    public void resumeJob(String jobName, String group) throws SchedulerException {
        scheduler.resumeJob(new JobKey(jobName, group));
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, you can expose this service via REST to manage jobs dynamically:

@RestController
@RequestMapping("/api/jobs")
public class JobController {
    @Autowired private DynamicJobService service;

    @PostMapping("/create")
    public String create(@RequestParam String name, @RequestParam String cron) throws Exception {
        service.scheduleJob(name, "default", cron);
        return "Job scheduled!";
    }

    @DeleteMapping("/{name}")
    public String delete(@PathVariable String name) throws Exception {
        service.deleteJob(name, "default");
        return "Job deleted!";
    }
}
Enter fullscreen mode Exit fullscreen mode

9. Comparing @Scheduled vs Quartz

Feature @Scheduled Quartz
Simplicity ✅ Very easy ❌ More setup
Dynamic runtime scheduling ❌ No ✅ Yes
Persistent jobs ❌ No ✅ Yes
Clustering ❌ No ✅ Yes
Cron syntax ✅ Yes ✅ Yes
Runtime control (pause/resume) ❌ No ✅ Yes
Distributed safe ❌ No ✅ Yes

10. Pro Tips

  • Use Quartz JDBC store for production.
  • Use Spring’s SchedulerFactoryBean for fine-grained control.
  • Wrap Quartz jobs in transactional service layers when interacting with DB.
  • Use UUIDs or composite keys for job identity.
  • Combine with Spring Events or Redis Pub/Sub to trigger jobs remotely.

Summary

Concept Tool
Basic periodic tasks @Scheduled
Dynamic, persistent, cluster-safe jobs Quartz
Want both? Start with simple scheduling → move to Quartz as you grow

Top comments (0)