Skip to content

etcd-recipes

Distributed coordination primitives for etcd v3 on the JVM — locks, leader election, barriers, queues, caches, counters and service discovery, for Kotlin and Java.

It is, in spirit, what Curator is for ZooKeeper: etcd gives you a consistent key/value store with leases, watches and atomic transactions; etcd-recipes turns those primitives into the coordination patterns you actually reach for.

connectToEtcd(listOf("http://localhost:2379")).use { client ->
  DistributedMutex(client, "/locks/orders").use { mutex ->
    mutex.withLock {
      // Exactly one process in the cluster is here at a time.
    }
  }
}

Get started Browse the recipes

What's in the box

  • LocksDistributedMutex, DistributedReadWriteLock, DistributedSemaphore

  • Leader electionLeaderSelector, LeaderLatch, LeaderObserver

  • Barriers — simple, counted, and double barriers

  • Queues — FIFO, priority, and an at-least-once work queue with dead letters

  • CachesNodeCache<T> for one key, PathChildrenCache for a prefix

  • CountersDistributedAtomicLong

  • Service discovery — registry, cache, and a load-balancing provider

  • Coroutines — suspending twins and Flow event surfaces

Why it might suit you

It is a thin layer, not a wrapper. The recipes are built on Kotlin extensions over jetcd, and jetcd's Client is never hidden from you. Use a recipe where it helps and drop to the raw API where it doesn't — they compose, because the recipes are written against the same extensions you get.

Kotlin-first, genuinely Java-usable. Every extension file carries a @JvmName facade, constructors are @JvmOverloads, and listeners are SAM interfaces. Most pages here show both languages side by side. See the Java guide for the specifics — and for the handful of things (coroutines, withLock { }) that really are Kotlin-only.

Failure is part of the API, not an afterthought. A distributed lock can be lost while you hold it. The recipes tell you when that happens rather than pretending it cannot: lock-lost listeners, connection state, unlock() returning false, and a deliberate policy on which leases self-heal and which must not. That last page is the one to read if you read only one.

Observable by default. An EtcdMetrics SPI with no-op defaults, a Micrometer binding, background-exception listeners, and SLF4J MDC context on worker threads.

Every example here is compiled

The code on this site is not typed into Markdown files. Each example is a real source file in a Gradle test source set, embedded at build time. ./gradlew compileTestKotlin compileTestJava type-checks all of them against the actual API on every CI run, so an example on this site cannot drift out of sync with the library. If you are curious how that is wired up, it is described in the website README.

Install

dependencies {
  implementation("com.pambrose:etcd-recipes-core:0.12.0")
}
dependencies {
  implementation 'com.pambrose:etcd-recipes-core:0.12.0'
}
<dependency>
  <groupId>com.pambrose</groupId>
  <artifactId>etcd-recipes-core</artifactId>
  <version>0.12.0</version>
</dependency>

Optional modules — Spring Boot, Ktor, Jackson and Micrometer — are listed in Integrations.

Compatibility

etcd v3
Java 17+
Built on jetcd
License Apache 2.0