Apache Camel: Timer & Quartz Scheduling

Camel provides two components for triggering routes on a schedule: the lightweight Timer component for fixed-rate intervals, and the Quartz component for full cron scheduling. Quartz also supports clustered execution: only one node in a cluster fires the trigger, preventing duplicate processing.

Contents

Timer Component Basics

The Timer component is included in camel-core and fires at a fixed rate. Use it whenever simple interval-based triggering is sufficient.

Java
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class HeartbeatRoute extends RouteBuilder {

    @Override
    public void configure() {

        // Fire immediately, then every 10 seconds
        from("timer:heartbeat?period=10000")
            .log("Heartbeat at ${date:now:HH:mm:ss}")
            .bean(HealthCheckService.class, "check");

        // Fire once after a 5-second delay, then stop
        from("timer:oneShot?delay=5000&repeatCount=1")
            .log("One-shot timer fired")
            .to("direct:startup-task");
    }
}
The timer name (e.g. heartbeat) is a logical name. Two routes sharing the same name share the same underlying Java timer thread.

Timer Options

Key options that control the timer's firing behaviour.

Java
// Full example using all common options
from("timer:batchJob"
    + "?delay=1000"           // wait 1 s before first fire
    + "&period=60000"         // fire every 60 s after that
    + "&repeatCount=10"       // stop after 10 firings (0 = infinite)
    + "&fixedRate=true"       // fixed-rate (vs fixed-delay) scheduling
    + "&daemon=true")         // use daemon thread (JVM can exit freely)
    .log("Batch job triggered β€” firing ${header.CamelTimerCounter}")
    .to("direct:processBatch");
Java
// Useful timer headers available inside the route
from("timer:report?period=5000")
    .process(exchange -> {
        Long counter    = exchange.getIn().getHeader("CamelTimerCounter",   Long.class);
        Long firedTime  = exchange.getIn().getHeader("CamelTimerFiredTime", Long.class);
        System.out.printf("Fire #%d at %tT%n", counter, firedTime);
    });

Quartz Component Setup

Add the Quartz starter, then use the quartz component as a consumer. Quartz is backed by the Quartz Scheduler library and supports full cron syntax.

XML
<dependency>
    <groupId>org.apache.camel.springboot</groupId>
    <artifactId>camel-quartz-starter</artifactId>
</dependency>
Java
@Component
public class ScheduledReportRoute extends RouteBuilder {

    @Override
    public void configure() {

        // Run every day at 06:00
        from("quartz://reports/daily?cron=0+0+6+*+*+?")
            .log("Generating daily report")
            .to("direct:generateReport");
    }
}
The Quartz endpoint URI format is quartz://groupName/jobName?cron=.... The group and job name together uniquely identify the trigger in the Quartz scheduler.

Quartz Cron Expressions

Quartz uses a 6-field cron format: Seconds Minutes Hours DayOfMonth Month DayOfWeek. The + character replaces spaces in URI query strings.

Java
// Every 30 seconds
from("quartz://jobs/every30s?cron=0/30+*+*+*+*+?")
    .to("direct:quickTask");

// Every weekday (Mon–Fri) at 09:00
from("quartz://jobs/weekdayMorning?cron=0+0+9+?+*+MON-FRI")
    .to("direct:morningTask");

// First day of every month at midnight
from("quartz://jobs/monthStart?cron=0+0+0+1+*+?")
    .to("direct:monthlyTask");

// Every hour, at :15 past
from("quartz://jobs/hourlySummary?cron=0+15+*+*+*+?")
    .to("direct:hourlySummary");
In Quartz cron, DayOfMonth and DayOfWeek cannot both be specified: one must be ? (no specific value). Use ? in the field you want to ignore.

Clustered Scheduling

In a multi-node deployment you usually want only one node to fire a trigger. Enable Quartz clustering with a JDBC JobStore so all nodes share trigger state via a shared database.

Properties
# application.properties β€” configure Quartz to use a database JobStore
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.initialize-schema=always

# Quartz clustering properties (placed in quartz.properties or via Spring)
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=20000
Java
// Mark the route as stateful so Quartz knows to cluster it
from("quartz://cluster/nightly?cron=0+0+2+*+*+?&stateful=true")
    .log("Running nightly job on node: ${sys.HOSTNAME}")
    .to("direct:nightlyBatch");
With clustering enabled and stateful=true, Quartz uses row-level locks in the shared database so exactly one cluster node executes the job per trigger.

Timer vs Quartz: When to Use Which

The right choice depends on scheduling complexity and deployment topology.

Java
/*
  Timer β€” use when:
  ─────────────────────────────────────────────────────
  β€’ Simple fixed-rate or fixed-delay intervals
  β€’ Single-node deployment
  β€’ No persistence needed across restarts
  β€’ Built into camel-core, zero extra dependency

  Quartz β€” use when:
  ─────────────────────────────────────────────────────
  β€’ Cron scheduling (specific times / days)
  β€’ Multi-node / clustered deployment
  β€’ Triggers must survive application restarts
  β€’ You need misfire instructions (what to do if a fire
    was missed while the app was down)
*/

// Timer β€” simplest interval job
from("timer:poll?period=30000")
    .to("direct:pollFeed");

// Quartz β€” same job but survives restarts & runs in cluster
from("quartz://feed/poll?cron=0/30+*+*+*+*+?&stateful=true")
    .to("direct:pollFeed");