Skip to content

Getting started

Requirements

Java 17 or newer
etcd v3

Add the dependency

The core library is all you need for every recipe on this site.

repositories {
  mavenCentral()
}

dependencies {
  implementation("com.pambrose:etcd-recipes-core:0.12.0")
}
repositories {
  mavenCentral()
}

dependencies {
  implementation 'com.pambrose:etcd-recipes-core:0.12.0'
}
[versions]
etcd-recipes = "0.12.0"

[libraries]
etcd-recipes-core = { module = "com.pambrose:etcd-recipes-core", version.ref = "etcd-recipes" }
<dependencies>
  <dependency>
    <groupId>com.pambrose</groupId>
    <artifactId>etcd-recipes-core</artifactId>
    <version>0.12.0</version>
  </dependency>
</dependencies>

The optional modules — Spring Boot, Ktor, Jackson, Micrometer — are listed with their coordinates in Integrations.

Run an etcd

You need an etcd to talk to. The quickest local one:

etcd --listen-client-urls=http://localhost:2379 \
     --advertise-client-urls=http://localhost:2379

Or with Docker:

docker run --rm -p 2379:2379 \
  quay.io/coreos/etcd:v3.5.17 \
  etcd --listen-client-urls=http://0.0.0.0:2379 \
       --advertise-client-urls=http://localhost:2379

Working in a clone of the repo?

./etcd-start.sh (or make etcd-start) starts one with those flags, and ./etcd-stop.sh (or make etcd-stop) shuts it down gracefully. The data directory default.etcd/ is gitignored.

Your first program

Connect, write a key, read it back:

// `use` closes the client; the recipes never take ownership of it for you.
connectToEtcd(["http://localhost:2379"]).use { client ->
  client.putValue("/greeting", "hello")
  logger.info { client.getValue("/greeting", "<absent>") }
}
// The Kotlin extensions are reached through their @JvmName facades:
// ClientUtils.connectToEtcd, KVUtils.putValue, and so on.
try (Client client = connectToEtcd(List.of("http://localhost:2379"))) {
  putValue(client, "/greeting", "hello");
  System.out.println(getValue(client, "/greeting", "<absent>"));
}

Two things in that snippet are worth naming now, because they hold everywhere:

  • You own the Client. connectToEtcd hands you a jetcd Client and closing it is your job — hence use / try-with-resources. Recipes take a Client; they never close one for you.
  • Java goes through a facade. The Kotlin extensions live in files annotated @file:JvmName("ClientUtils"), @file:JvmName("KVUtils") and so on, which is what makes KVUtils.putValue(client, ...) available. The Java guide maps the lot.

Now do something distributed

The point of the library is the recipes. Here is a mutex — one process in the whole cluster inside the block at a time:

DistributedMutex(client, "/locks/orders").use { mutex ->
  mutex.withLock {
    // Only one client across the cluster runs this at a time.
    logger.info { "Critical section" }
  }
}
try (DistributedMutex mutex = new DistributedMutex(client, "/locks/orders")) {
  mutex.lock();
  try {
    // Only one client across the cluster runs this at a time.
    System.out.println("Critical section");
  } finally {
    mutex.unlock();
  }
}

Run it in two JVMs at once and watch them take turns.

Where to go next

  • Connecting — auth, TLS, namespaces, timeouts, health checks

  • Core concepts — the lifecycle every recipe shares, and how failures surface. Read this one.

  • Recipes — the catalog

  • Java guide — if you are not writing Kotlin