java 60 lines · 8 steps

Distributed scheduled jobs with Spring locks

A scheduled reconciliation job uses a JDBC-backed distributed lock so only one node in a cluster runs it at a time.

Explained by highlit
1@Component
2public class InventoryReconciliationJob {
3 
4 private static final Logger log = LoggerFactory.getLogger(InventoryReconciliationJob.class);
5 private static final String LOCK_KEY = "inventory-reconciliation";
6 
7 private final LockRegistry lockRegistry;
8 private final InventoryReconciler reconciler;
9 
10 public InventoryReconciliationJob(LockRegistry lockRegistry, InventoryReconciler reconciler) {
11 this.lockRegistry = lockRegistry;
12 this.reconciler = reconciler;
13 }
14 
15 @Scheduled(cron = "0 */15 * * * *")
16 public void reconcile() {
17 Lock lock = lockRegistry.obtain(LOCK_KEY);
18 boolean acquired;
19 try {
20 acquired = lock.tryLock(5, TimeUnit.SECONDS);
21 } catch (InterruptedException e) {
22 Thread.currentThread().interrupt();
23 log.warn("Interrupted while acquiring reconciliation lock");
24 return;
25 }
26 
27 if (!acquired) {
28 log.debug("Another node holds the reconciliation lock; skipping this run");
29 return;
30 }
31 
32 try {
33 ReconciliationResult result = reconciler.run();
34 log.info("Reconciled {} SKUs, {} adjustments applied",
35 result.scanned(), result.adjustments());
36 } finally {
37 try {
38 lock.unlock();
39 } catch (IllegalStateException e) {
40 log.warn("Reconciliation lock expired before release: {}", e.getMessage());
41 }
42 }
43 }
44 
45 @Configuration
46 static class LockConfig {
47 
48 @Bean
49 public DefaultLockRepository lockRepository(DataSource dataSource) {
50 DefaultLockRepository repository = new DefaultLockRepository(dataSource);
51 repository.setTimeToLive(120_000);
52 return repository;
53 }
54 
55 @Bean
56 public JdbcLockRegistry lockRegistry(LockRepository lockRepository) {
57 return new JdbcLockRegistry(lockRepository);
58 }
59 }
60}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A shared lock backed by a database turns a cluster-wide scheduled job into a single-executor operation without a dedicated coordinator.
  2. 2Always bound lock acquisition with a timeout and release in a finally block so a failed run never leaves the lock held.
  3. 3A time-to-live on the lock guards against nodes that crash mid-run, letting another instance recover the schedule.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Distributed scheduled jobs with Spring locks — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code