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
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");
}
}
heartbeatTimer Options
Key options that control the timer's firing behaviour.
// 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");
// 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
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-quartz-starter</artifactId>
</dependency>
@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");
}
}
quartz://groupName/jobName?cron=...Quartz Cron Expressions
Quartz uses a 6-field cron format: Seconds Minutes Hours DayOfMonth Month DayOfWeek+
// 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");
DayOfMonthDayOfWeek??Clustered Scheduling
In a multi-node deployment you usually want only one node to fire a trigger. Enable Quartz clustering with a JDBC JobStore
# 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
// 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");
stateful=trueTimer vs Quartz: When to Use Which
The right choice depends on scheduling complexity and deployment topology.
/*
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");