Connecting¶
Everything in the library takes a jetcd Client. This page is about getting one.
connectToEtcd¶
The ClientUtils facade (common/ClientExtensions.kt) offers four overloads: a URL list
or an EtcdConnectionConfig, each in a plain form and a scoped block form.
// 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>"));
}
The scoped form closes the client for you when the block returns — including on a throw:
// The block form closes the client when the block returns, even on a throw.
val greeting =
connectToEtcd(["http://localhost:2379"]) { client ->
client.getValue("/greeting", "<absent>")
}
logger.info { greeting }
You own the client's lifetime
A recipe never closes the Client you hand it — closing a recipe closes the recipe,
not your connection. That is deliberate: one client is normally shared by many
recipes. The exception is the Ktor plugin, which closes a
client it created itself but never one you injected.
Under the hood both forms apply ClientBuilder.withRecipeDefaults(), the settings the
recipes assume.
EtcdConnectionConfig¶
For anything beyond a URL list — credentials, TLS, a namespace — use the config object:
val config =
EtcdConnectionConfig(
endpoints = ["https://etcd-1:2379", "https://etcd-2:2379"],
user = "app",
password = System.getenv("ETCD_PASSWORD"),
// Every key this client touches is transparently prefixed with /prod.
namespace = "/prod",
// Note: java.time.Duration here, not kotlin.time.Duration.
connectTimeout = Duration.ofSeconds(5),
retryMaxDuration = Duration.ofSeconds(30),
tls =
EtcdTlsConfig(
caCertPath = "/etc/etcd/ca.crt",
clientCertPath = "/etc/etcd/client.crt",
clientKeyPath = "/etc/etcd/client.key",
),
)
connectToEtcd(config).use { client ->
logger.info { "Reachable: ${client.ping()}" }
}
EtcdTlsConfig tls =
new EtcdTlsConfig("/etc/etcd/ca.crt", "/etc/etcd/client.crt", "/etc/etcd/client.key");
EtcdConnectionConfig config =
new EtcdConnectionConfig(
List.of("https://etcd-1:2379", "https://etcd-2:2379"),
"app", // user
System.getenv("ETCD_PASSWORD"), // password
"/prod", // namespace
Duration.ofSeconds(5), // connectTimeout (java.time.Duration)
Duration.ofSeconds(30), // retryMaxDuration
tls);
try (Client client = connectToEtcd(config)) {
System.out.println("Reachable: " + ping(client));
}
| Field | Default | Notes |
|---|---|---|
endpoints |
— | required |
user / password |
null |
etcd RBAC |
namespace |
null |
transparent key prefix |
connectTimeout |
5s | java.time.Duration |
retryMaxDuration |
30s | java.time.Duration |
tls |
null |
EtcdTlsConfig |
These are java.time.Duration, not kotlin.time.Duration
EtcdConnectionConfig is the one place in the library that uses java.time.Duration
— it is the type the underlying jetcd builder wants. The recipes themselves take
kotlin.time.Duration (with (long, TimeUnit) overloads for Java). If Kotlin tells
you 5.seconds is the wrong type here, that is why: you want Duration.ofSeconds(5).
Namespaces¶
Setting namespace = "/prod" transparently prefixes every key this client touches. A
recipe at /locks/orders really lives at /prod/locks/orders, and nothing in your code
changes. It is the cleanest way to share one etcd cluster between environments or tenants.
Tip
The namespace is a property of the client, so it applies uniformly to every recipe built on it. Two clients with different namespaces will never see each other's keys — which is exactly what you want for isolation, and exactly the trap if you accidentally point a reader and a writer at different namespaces.
TLS and authentication¶
EtcdTlsConfig(caCertPath, clientCertPath, clientKeyPath) — all three are optional:
supply just caCertPath to verify the server against a private CA, or all three for
mutual TLS. Username/password auth is orthogonal and set via user/password.
Don't put the password in source
The snippet reads ETCD_PASSWORD from the environment. Do likewise, or pull it from
whatever secret manager you already run.
Dropping to the jetcd builder¶
Anything the config class does not model is reachable through the builder receiver:
// Drop to the jetcd ClientBuilder for anything the config class doesn't cover.
connectToEtcd(
urls = ["http://localhost:2379"],
initReceiver = { maxInboundMessageSize(8 * 1024 * 1024) },
).use { client ->
logger.info { "Connected" }
}
This is the library's general stance: it is a thin layer over jetcd, and jetcd is never hidden from you.
Health checks¶
Client.ping() performs an active, count-only GET and returns a Boolean:
It takes an optional RpcResilience so you can decide whether a health check retries.
For a liveness endpoint you usually do not want retries — you want a fast, honest
answer:
Recipes expose their own ping() plus a passive isHealthy(). The difference matters:
isHealthy() reports what the recipe already knows from its connection state without a
round trip, while ping() goes and asks. See
Connection state.
The Spring Boot starter wires ping() into an Actuator
health indicator for you.
Next¶
- Core concepts — the lifecycle every recipe shares
- Resilience — retries, timeouts, and reconnection