@Scheduled arranges callbacks in each application context; it does not elect a cluster-wide worker.
Spring @Scheduled on three replicas: the callback runs three times
Count application instances
When three parcel-service replicas all contain the same scheduled relay bean, each replica wakes on its own timer. A single-node test sees one call and can hide three-way production execution. Make the database claim the arbiter: atomically select due rows for one owner, retain a lease, and fence stale acknowledgments. The schedule merely asks each process to look for work.
Avoid overlapping local runs
A fixed-rate trigger can fire again while a slow previous run is still active if the scheduler has capacity. Use bounded claims and a local concurrency guard where needed, but keep the database lease because local guards cannot coordinate other replicas. The outbox worker supplies the state machine; shutdown handling explains why a lease may outlive a worker.
Prove handoff
Run three app instances against one database, pause the current owner after claim, and let its lease expire. Confirm a second owner takes over and the old owner cannot commit an acknowledgment. A test of only the @Scheduled method invocation cannot establish cluster safety.
Implementation sketch
@Scheduled(fixedDelayString = "${parcel.relay.delay.ms:7000}")
public void dispatchDueParcels() {
for (ClaimedParcel parcel : claims.claimDue(23)) {
relay.sendAndAcknowledge(parcel);
}
}Cost and verification
Every replica polls, adding database reads when idle. Small bounded claims and a suitable interval trade latency for load; leases require a clock and expiration policy.
Common Mistakes
- Do not treat @Scheduled as a cluster leader election mechanism.
- Do not hold a local mutex and assume other replicas cannot run.
- Do not acknowledge work after losing the lease or fencing token.
Read next
Spring TaskScheduler: poll committed outbox rows in the background, Spring outbox claims: lease expiry and token-fenced acknowledgement, Outbox acknowledgements: require the current lease token, Spring TaskScheduler shutdown: stop new polls and account for in-flight work.
