I was building a microservice that needed to run a nightly report across ten instances. Only one should do the work; the rest should wait. I thought about database locks, message queues, and even file-based markers. Then I remembered ZooKeeper. But raw ZooKeeper code is verbose and fragile. That is when Apache Curator saved my sanity. Coupled with Spring Boot, it felt like a cheat code. Let me show you why this combination matters and how you can use it.
Distributed coordination sounds intimidating, but it is just a set of rules that help multiple machines agree on something without constant arguing. ZooKeeper gives you a reliable, consistent key‑value store with watches and ephemeral nodes. Curator, the high‑level Java client, turns these low‑level knobs into reliable recipes. Spring Boot then wires everything together with auto‑configuration and dependency injection. You get production‑ready coordination without writing a single line of raw ZooKeeper API.
Have you ever wondered how a cluster of services decides which node is the leader? Or how you prevent two workers from processing the same job? These are common problems that distributed locking and leader election solve. Curator provides elegant implementations for both, and Spring Boot makes them feel like any other bean.
Let me walk you through a simple scenario. You have three instances of a Spring Boot application that need to run a scheduled task, but only one at a time. You can use Curator’s distributed lock. First, add the dependency:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.4.0</version>
</dependency>
Then configure a CuratorFramework bean. Spring Boot’s auto‑configuration can do this if you add the ZooKeeper connection string in application.properties:
spring.cloud.zookeeper.connect-string=localhost:2181
For Curator itself, I prefer manual configuration to keep things explicit. Here is a typical configuration class:
@Configuration
public class CuratorConfig {
@Bean(destroyMethod = "close")
public CuratorFramework curatorFramework() {
CuratorFramework client = CuratorFrameworkFactory.newClient(
"localhost:2181",
new ExponentialBackoffRetry(1000, 3)
);
client.start();
return client;
}
}
Now you can inject the lock wherever you need it. Here is a service that runs a critical batch job only when it holds the lock:
@Service
public class BatchService {
private final CuratorFramework client;
private final InterProcessLock lock;
public BatchService(CuratorFramework client) {
this.client = client;
this.lock = new InterProcessSemaphoreMutex(client, "/batch-lock");
}
@Scheduled(fixedRate = 60000)
public void runBatch() {
try {
if (lock.acquire(0, TimeUnit.SECONDS)) {
// critical work
System.out.println("Doing batch work on instance " + UUID.randomUUID());
} else {
System.out.println("Lock held by another instance, skipping");
}
} catch (Exception e) {
// handle error
} finally {
try { lock.release(); } catch (Exception ignored) {}
}
}
}
I remember the first time I ran three instances of this service. Only one printed “Doing batch work” each minute. The others skipped. No database overhead, no race conditions. It just worked. That is the beauty of combining Curator with Spring Boot – the coordination logic becomes almost invisible.
What about leader election? That is another common need. Perhaps you have a service that must process events in order, and only the leader should consume from a queue. Curator’s LeaderSelector does this cleanly. You implement a callback that runs when your instance becomes the leader:
@Component
public class LeaderService implements LeaderSelectorListener {
private final LeaderSelector leaderSelector;
public LeaderService(CuratorFramework client) {
this.leaderSelector = new LeaderSelector(client, "/leader-election", this);
leaderSelector.autoRequeue(); // re‑enter election after relinquishing
leaderSelector.start();
}
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println("I am the leader now");
try {
Thread.sleep(Long.MAX_VALUE); // hold leadership until interrupted
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@PreDestroy
public void stop() {
leaderSelector.close();
}
}
When the leader crashes or shuts down, another instance takes over automatically. The autoRequeue method ensures that the same instance can become leader again after giving up. This pattern is widely used in distributed job schedulers, caching layers, and event processors.
Have you ever struggled with dynamic configuration changes across a cluster? ZooKeeper can store configuration keys, and Curator provides a TreeCache that watches for changes. Integrate it with Spring Cloud’s @RefreshScope and you get live config updates without restarting your application. Here is a minimal example:
@Component
public class ConfigWatcher {
private final CuratorFramework client;
public ConfigWatcher(CuratorFramework client) {
this.client = client;
watchConfig();
}
private void watchConfig() {
TreeCache cache = TreeCache.newBuilder(client, "/config").build();
cache.getListenable().addListener((client, event) -> {
if (event.getType() == TreeCacheEvent.Type.NODE_UPDATED) {
System.out.println("Config updated: " + event.getData().getPath()
+ " -> " + new String(event.getData().getData()));
// trigger refresh of Spring beans here
}
});
try {
cache.start();
} catch (Exception e) {
// handle
}
}
}
You can then extend this with Spring’s event mechanism to push changes to any bean that needs them.
Now, you might ask: why not just use a relational database for locking? Databases work, but they introduce latency and can become contention points. ZooKeeper is built for this. Its ephemeral nodes automatically clean up locks if the client dies, preventing deadlocks. Curator adds retries, connection management, and higher‑level primitives. Spring Boot gives you a familiar programming model to glue everything together.
I have used this stack in production to coordinate a cluster of data ingestion workers. Each worker tried to acquire a lock on a partition key. Only the holder processed the partition. If a worker failed, the lock disappeared, and another worker picked it up within seconds. Failover was fluid.
Let me also warn you about common pitfalls. Never hold a ZooKeeper lock for more than a few seconds without releasing – it can cause cascading delays. Use InterProcessMutex with a timeout instead of indefinite blocking. Also, ensure your ZooKeeper ensemble is highly available; a single node is a single point of failure. Curator helps with connection state handling, but you still need a reliable ensemble.
For testing, I recommend using the Curator TestingServer or the embedded ZooKeeper server. Spring Boot’s test slices can wire up a minimal context with a testing ZooKeeper instance. This lets you verify locking behavior without spinning up external infrastructure.
@Test
public void testLock() throws Exception {
try (TestingServer server = new TestingServer()) {
CuratorFramework client = CuratorFrameworkFactory.newClient(
server.getConnectString(),
new ExponentialBackoffRetry(1000, 3)
);
client.start();
InterProcessLock lock = new InterProcessMutex(client, "/test-lock");
assertTrue(lock.acquire(2, TimeUnit.SECONDS));
lock.release();
}
}
I keep this test handy whenever I change any coordination logic. It catches regressions early.
To wrap up: integrating Spring Boot with Apache Curator gives you a robust, production‑tested way to manage distributed coordination without drowning in low‑level ZooKeeper code. You get leader election, distributed locking, configuration watching, and more – all inside the comfortable Spring environment. If you are building a distributed system, this combination will save you many headaches.
If you found this useful, I would appreciate it if you like, share, and comment below. Are there other distributed coordination problems you are wrestling with? Let me know – I might write the next article about them.
As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva