Key-value & sub-document operations
|
This section documents the current Couchbase Server 7.6.x line as published at the Couchbase Server documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. Some capabilities (Enterprise-Edition-only Analytics, auditing, encryption at rest, the Backup service and rack-zone awareness, and Capella-only App Services and Columnar) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Couchbase iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
The key-value (KV) API is the fastest way in and out of Couchbase: it addresses one item by key and skips the query engine entirely. This page covers the connection path, the CRUD verbs, the atomic numeric and binary operations, and the sub-document API that reads or changes part of a document in a single round-trip.
Connecting: Cluster to Collection
Access always narrows through the same chain: a Cluster (a connection to the whole deployment) yields a
Bucket, which yields a Scope, which yields a Collection. The default collection
(_default scope, _default collection) exists in every bucket for backward compatibility.
Cluster cluster = Cluster.connect("couchbase://127.0.0.1", "appuser", "s3cr3t");
Bucket bucket = cluster.bucket("travel-sample");
bucket.waitUntilReady(Duration.ofSeconds(10));
Scope scope = bucket.scope("inventory");
Collection collection = scope.collection("airline");
# https://docs.couchbase.com/java-sdk/current/howtos/kv-operations.html
The CRUD verbs
| Operation | Behavior |
|---|---|
|
Create only. Fails with |
|
Create or overwrite unconditionally. |
|
Overwrite only. Fails with |
|
Fetch the value and metadata. Fails with |
|
Delete. Fails with |
|
Cheap existence check; returns a result whose |
|
Fetch the value and reset the expiry in one operation. |
try {
collection.insert("airline_1010", JsonObject.create().put("name", "40-Mile Air").put("iata", "Q5"));
} catch (DocumentExistsException e) {
// key already present -- fall back to replace-with-CAS or upsert
}
GetResult r = collection.get("airline_1010");
JsonObject air = r.contentAsObject();
long cas = r.cas();
collection.replace("airline_1010", air.put("country", "United States"),
ReplaceOptions.replaceOptions().cas(cas)); // fails with CasMismatchException if changed meanwhile
boolean present = collection.exists("airline_9999").exists(); // false, no exception
collection.remove("airline_1010");
Every operation takes an options object for timeout, durability, expiry, and CAS. Errors are typed: catch
DocumentNotFoundException, DocumentExistsException, CasMismatchException,
DurabilityImpossibleException, TimeoutException rather than inspecting status codes. See
KV operations for the full result and
error model, and Data Service fundamentals
for how a KV request is routed.
Atomic counters, binary append/prepend, range scan
increment / decrement atomically adjust an integer document held as an ASCII number. Both accept an
initial value used only when the key does not yet exist, and a delta:
# Server-side atomic add; no read-modify-write race between clients
CounterResult c = collection.binary().increment("counter:visits",
IncrementOptions.incrementOptions().initial(0).delta(1));
long current = c.content();
collection.binary().decrement("counter:seats-left",
DecrementOptions.decrementOptions().initial(180).delta(2));
# https://docs.couchbase.com/java-sdk/current/howtos/kv-operations.html
append / prepend add bytes to the end or start of a non-JSON (binary/string) value. They must not be
used on JSON documents — the result would not be valid JSON. For structured edits use the sub-document API
below.
KV Range Scan (7.6) streams every key, or every key in a prefix, directly from the data service without an index. It is meant for background/export jobs, not low-latency request paths:
# Stream all keys beginning with "airline_" from the collection
ScanResult scan = collection.scan(ScanType.prefixScan("airline_"),
ScanOptions.scanOptions().idsOnly(true));
scan.forEach(item -> process(item.id()));
# https://docs.couchbase.com/java-sdk/current/howtos/kv-operations.html
For throughput, the SDKs expose async (CompletableFuture) and reactive (Mono / Flux) variants of
every KV call via collection.async() and collection.reactive(). A "bulk" operation is simply many async
calls issued together and awaited as a batch — there is no separate multi-get wire command:
List<CompletableFuture<GetResult>> futures = keys.stream()
.map(k -> collection.async().get(k))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
The sub-document API
lookupIn (read) and mutateIn (write) operate on paths inside a JSON document, so the server sends or
receives only the fragment involved instead of the whole value. This cuts bandwidth and, for mutateIn,
removes the fetch-modify-store race: the change is applied server-side in one atomic step, optionally guarded
by CAS.
Per-path operations:
| Spec | Effect |
|---|---|
|
Read a value at a path, test a path, or count an array/object at a path. |
|
Set, add-only, overwrite-only, or delete a field at a path. |
|
Add elements at the end, start, or a given index of an array. |
|
Append a scalar only if it is not already present in the array. |
|
Atomically adjust a numeric field (sub-document counter). |
# Read three fields (and one XATTR) without transferring the whole document
LookupInResult look = collection.lookupIn("airline_10", Arrays.asList(
LookupInSpec.get("name"),
LookupInSpec.get("country"),
LookupInSpec.exists("icao"),
LookupInSpec.get("$document.exptime").xattr())); # virtual XATTR: server-computed expiry
String name = look.contentAs(0, String.class);
boolean hasIcao = look.exists(2);
# Mutate several paths in one atomic round-trip
collection.mutateIn("airline_10", Arrays.asList(
MutateInSpec.upsert("updatedAt", Instant.now().toString()),
MutateInSpec.arrayAddUnique("routes", "SFO-JFK"),
MutateInSpec.increment("version", 1),
MutateInSpec.upsert("audit.by", "svc-import").xattr().createPath()),
MutateInOptions.mutateInOptions().cas(knownCas));
# https://docs.couchbase.com/java-sdk/current/howtos/subdocument-operations.html
createPath tells a mutation to create any missing intermediate objects along the path (so
audit.by works even when audit did not exist). Adding .xattr() targets an
Extended Attribute instead of the document body.
A mutateIn can address at most 16 paths per call.
Why it matters: fetching a 200 KB document to change one timestamp and writing it back moves 400 KB over the
wire and can lose a concurrent update. The equivalent mutateIn moves a few hundred bytes and cannot clobber
fields it does not name. See
Sub-Document operations for
path syntax, macro values, and the per-path error model.
Contrast with MongoDB
Couchbase’s mutateIn path operations play the role MongoDB’s update operators play: both change part of a
document server-side without a client round-trip for the read. The main differences are that Couchbase
addresses exactly one document by key (no query filter selects the target), a single mutateIn batches
heterogeneous path ops with an optional CAS guard, and array de-duplication is an explicit arrayAddUnique
rather than $addToSet. For the operator-update model and multi-document update semantics, see
MongoDB Updating & Deleting.
Continue with Concurrency, locking & durable writes.