Spring Data Couchbase

This section documents Spring Boot 4.1.x and Spring Framework 7.0.x on the Java 17/21+ baseline, as described by the official Spring Boot reference documentation and each sub-project’s own site (Spring Data, Spring for Apache Kafka, Micrometer, Project Reactor, springdoc-openapi, Spring gRPC, and the others listed in this section’s Bibliography) — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Spring Boot and its ecosystem continue to evolve; the examples here target the current 4.1.x / 7.0.x releases.

This section’s bibliography lists the reference material consulted while preparing these pages.

Spring Data Couchbase layers repository abstractions, SQL++ query derivation, and object mapping on top of the Couchbase Java SDK, so a Spring Boot application can talk to Couchbase buckets/scopes/collections the same way it would talk to a JPA or MongoDB data store.

Dependency and connection setup

The spring-boot-starter-data-couchbase starter brings in Spring Data Couchbase and the Couchbase Java SDK, and spring.couchbase. / spring.data.couchbase. properties configure the cluster connection:

// build.gradle / pom.xml: org.springframework.boot:spring-boot-starter-data-couchbase
spring.couchbase.connection-string=couchbase://127.0.0.1
spring.couchbase.username=Administrator
spring.couchbase.password=password
spring.data.couchbase.bucket-name=travel-sample
spring.data.couchbase.scope-name=inventory

Auto-configuration exposes a Cluster and a CouchbaseTemplate bean once these properties (or an explicit AbstractCouchbaseConfiguration subclass) are present, and @EnableCouchbaseRepositories activates repository scanning.

Entity mapping

A repository entity is a plain class annotated with @Document, an @Id, and optionally @Field for explicit property names:

@Document
public class Airline {

    @Id
    private String id;

    @Field
    private String name;

    @Field
    private String country;

    // getters/setters omitted
}

Couchbase documents are JSON, so nested objects and collections on the entity map naturally to nested JSON objects and arrays; @Field is only needed when the Java property name should differ from the stored JSON key.

Polymorphic documents and inheritance

Like MongoDB (covered there), Couchbase buckets are schema-less JSON stores, so there is no relational @Inheritance strategy to pick — the same collection can already hold documents of different shapes. Spring Data Couchbase solves the same problem MongoDB does: recovering the right Java subclass when reading a document back. It writes a hidden _class discriminator field into every document by default, and @TypeAlias shortens that discriminator from the full Java class name to a short, rename-safe string:

public abstract class Payment {

    @Id
    private String id;

    private BigDecimal amount;
}

@Document
@TypeAlias("card")
public class CardPayment extends Payment {

    private String last4Digits;
}

@Document
@TypeAlias("bank_transfer")
public class BankTransferPayment extends Payment {

    private String iban;
}

A repository declared against the abstract base type transparently returns the correct concrete subclass for each document, resolved from its stored _class/@TypeAlias value — exactly as with Spring Data MongoDB.

CouchbaseRepository

CouchbaseRepository<T, ID> extends the familiar CrudRepository / PagingAndSortingRepository hierarchy, giving save, findById, findAll, deleteById, and paging/sorting for free:

public interface AirlineRepository extends CouchbaseRepository<Airline, String> {
}
@Service
public class AirlineService {

    private final AirlineRepository airlines;

    public AirlineService(AirlineRepository airlines) {
        this.airlines = airlines;
    }

    public Airline create(Airline airline) {
        return airlines.save(airline);
    }

    public Optional<Airline> findOne(String id) {
        return airlines.findById(id);
    }

    public void delete(String id) {
        airlines.deleteById(id);
    }
}

Unlike Spring Data JPA and Spring Data MongoDB, CouchbaseRepository does not currently support keyset scrolling: it extends only PagingAndSortingRepository/CrudRepository, with no Window<T>/ScrollPosition counterpart (see Keyset scrolling with Window<T> for the modules that do support it). To get the offset-free keyset pattern against Couchbase today, drop to a @Query-annotated SQL++ seek query directly, the same construct documented in Avoiding large offsets — see Pagination: Offset vs. Keyset for why this matters.

Derived N1QL/SQL++ queries

Method names are parsed into N1QL/SQL++ predicates the same way Spring Data derives JPQL or Mongo queries from a method signature:

public interface AirlineRepository extends CouchbaseRepository<Airline, String> {

    List<Airline> findByCountry(String country);

    List<Airline> findByCountryAndNameStartingWith(String country, String prefix);

    long countByCountry(String country);

    boolean existsByName(String name);
}

Each derived method compiles to a SELECT …​ FROM \`bucket\.`scope\`.`collection\` WHERE …​` statement against the entity’s mapped collection, using the primary N1QL/SQL++ index (or a suitable secondary index) to satisfy the predicate.

@Query methods

When derivation is not expressive enough, @Query accepts a literal SQL++ statement with SpEL-style placeholders for the entity’s bucket (#\{#n1ql.bucket\}), scope, and collection, plus positional/named parameters:

public interface AirlineRepository extends CouchbaseRepository<Airline, String> {

    @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND country = $1 ORDER BY name LIMIT $2")
    List<Airline> findTopAirlinesByCountry(String country, int limit);

    @Query("SELECT RAW COUNT(*) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} AND country = $country")
    long countAirlinesInCountry(@Param("country") String country);
}

\{#n1ql.selectEntity\} and \{#n1ql.filter\} expand to the correct keyspace and type-discriminator predicate for the mapped entity, so a custom statement still restricts itself to matching documents in a mixed collection.

Buckets, scopes, and collections mapping

Couchbase organizes documents in a bucket → scope → collection hierarchy; @Scope and @Collection on an entity pin it to a specific scope/collection instead of the connection-wide default:

@Document
@Scope("inventory")
@Collection("airline")
public class Airline {

    @Id
    private String id;

    @Field
    private String name;

    @Field
    private String country;
}

A repository whose entity carries @Scope/@Collection automatically targets that keyspace for every derived query, @Query expansion, and save/findById call, without repeating the scope/collection name in application code. The modeling and querying concepts behind buckets, scopes, and collections themselves — naming, indexing implications, multi-tenancy patterns — are not repeated here; see Couchbase Reference and, for the underlying storage layout, Buckets, Scopes and Collections.

Full-text and vector search integration

Spring Data Couchbase repositories can also expose Couchbase’s Full-Text Search (FTS) and vector search indexes through @Query-style search methods and the SearchQuery/Vector builder APIs from the underlying SDK, letting a repository method run a text or similarity search alongside plain N1QL/SQL++ finders:

public interface HotelRepository extends CouchbaseRepository<Hotel, String> {

    @Query("#{#n1ql.selectEntity} WHERE SEARCH(#{#n1ql.bucket}, $1) AND #{#n1ql.filter}")
    List<Hotel> searchByDescription(String ftsQuery);
}
@Service
public class HotelSearchService {

    private final Cluster cluster;

    public HotelSearchService(Cluster cluster) {
        this.cluster = cluster;
    }

    public SearchResult searchHotels(String indexName, String text) {
        return cluster.searchQuery(indexName, SearchQuery.queryString(text));
    }
}

Building and tuning the FTS indexes, analytics collections, and eventing functions themselves is a Couchbase server/administration concern rather than a Spring Data one; see Couchbase Reference: Search, Analytics & Eventing for index design, analytics queries, and eventing functions.

Geospatial queries

Geospatial search is reached the same way as the text and vector search covered above: through the SDK’s geoDistance/geoBoundingBox/geoPolygon query builders against a geo-indexed field, rather than a dedicated repository keyword or annotation.

Mapping and indexing a geo point field

Unlike Spring Data MongoDB’s @GeoSpatialIndexed or Spring Data Neo4j’s native Point type, Spring Data Couchbase has no dedicated geo-mapping annotation or value type. A document’s geo field is just an ordinary mapped property — Couchbase documents are JSON, so, as the "Entity mapping" section above notes, a nested object maps naturally without any special handling:

@Document
public class Hotel {

    @Id
    private String id;

    @Field
    private String name;

    @Field
    private String description;

    @Field
    private GeoPoint geo;   // {"lon": ..., "lat": ...} -- a plain nested value, no geo-specific type needed

    // getters/setters omitted
}

public class GeoPoint {

    private double lon;
    private double lat;

    // getters/setters omitted
}

What makes geo queryable by distance or bounding box is not a Java annotation but the FTS index definition itself declaring that JSON path as a geopoint field — an index-service concern, the same as the name/ reviews.content text fields already shown in Couchbase Reference: Search, Analytics & Eventing:

PUT /api/index/hotels-idx
{
  "type": "fulltext-index",
  "sourceName": "travel-sample",
  "params": {
    "mapping": {
      "types": {
        "inventory.hotel": {
          "properties": {
            "geo": { "fields": [{ "name": "geo", "type": "geopoint" }] }
          }
        }
      }
    }
  }
}

Running bounding-box and distance queries

With hotels-idx indexing geo as above, geoBoundingBox/geoDistance on SearchQuery run the queries directly against it:

@Service
public class HotelGeoSearchService {

    private final Cluster cluster;

    public HotelGeoSearchService(Cluster cluster) {
        this.cluster = cluster;
    }

    public SearchResult findWithinBoundingBox(String indexName, double topLeftLon, double topLeftLat,
                                                double bottomRightLon, double bottomRightLat) {
        return cluster.searchQuery(indexName,
                SearchQuery.geoBoundingBox(topLeftLon, topLeftLat, bottomRightLon, bottomRightLat));
    }

    public SearchResult findNearby(String indexName, double lon, double lat, double distance, String unit) {
        return cluster.searchQuery(indexName, SearchQuery.geoDistance(lon, lat, distance + unit));
    }
}

Both builders query the field(s) the index maps as geopoint — there is no way to name a specific field from the query side beyond the index the search runs against, so a document mapping more than one geopoint field needs a matching, separately-indexed field name per query use case. See Couchbase Reference: Search, Analytics & Eventing for the geo distance/bounding-box/polygon FTS query types, and Couchbase Reference: Indexes and Views for the legacy spatial views index type — the underlying index/query theory is not repeated here.

Custom queries with CouchbaseTemplate

A repository is convenient but sometimes too high-level — fetching by a raw SQL++ statement with full control over consistency, or performing a fire-and-forget upsert/remove without loading an entity first. CouchbaseTemplate (and its reactive counterpart ReactiveCouchbaseTemplate) covers those cases:

@Repository
public class AirlineCustomDao {

    private final CouchbaseTemplate template;

    public AirlineCustomDao(CouchbaseTemplate template) {
        this.template = template;
    }

    public List<Airline> findByQuery(String country) {
        Query query = QueryCriteria.where("country").eq(country).asQuery();
        return template.findByQuery(Airline.class)
                .matching(query)
                .all();
    }

    public Airline upsert(Airline airline) {
        return template.upsertById(Airline.class).one(airline);
    }

    public void remove(String id) {
        template.removeById(Airline.class).one(id);
    }
}

upsertById/removeById bypass optimistic-locking checks that save/delete apply when an entity carries a @Version field, which is useful for bulk maintenance operations where last-write-wins is acceptable.

Dropping to the SDK directly

When even CouchbaseTemplate is too constrained — sub-document mutations, durability requirements, or N1QL/SQL++ query options not exposed by the template — inject the Couchbase Cluster/Collection beans that Spring Boot auto-configures and use the Java SDK directly:

@Repository
public class AirlineLowLevelDao {

    private final Collection collection;

    public AirlineLowLevelDao(Cluster cluster) {
        this.collection = cluster.bucket("travel-sample")
                .scope("inventory")
                .collection("airline");
    }

    public void bumpRating(String id, int delta) {
        collection.mutateIn(id, List.of(
                MutateInSpec.increment("rating", delta)
        ));
    }
}

See the Spring Data Couchbase template reference for the full CouchbaseTemplate / ReactiveCouchbaseTemplate API, and Couchbase Reference for the SDK-level operations (key-value, sub-document, transactions) that CouchbaseTemplate and direct SDK access both build on.

Optimistic locking with @Version

Couchbase already has a built-in optimistic-concurrency mechanism at the storage layer — the CAS (compare-and-swap) value described in Couchbase Reference: Concurrency, Locking & Durability. Spring Data Couchbase exposes it directly through @Version: no separate counter is maintained, the annotated field simply mirrors the document’s real CAS value:

@Document
public class Airline {

    @Id
    private String id;

    @Field
    private String name;

    @Version
    private long version;   // mirrors the document's CAS value -- never set this manually
}

Reading an Airline populates version with the document’s current CAS; saving it back sends that CAS value to Couchbase as a precondition on the write. If another writer already modified the document in between, the CAS the caller is holding is stale, the server rejects the write, and Spring Data surfaces it as OptimisticLockingFailureException. As the "Custom queries with CouchbaseTemplate" section above notes, upsertById/removeById deliberately bypass this check — reach for save/delete (or the repository) instead of the template’s direct methods whenever the CAS check should apply.

Transactions

Spring Data Couchbase integrates with Couchbase’s distributed ACID transactions through @Transactional on a service method, provided a CouchbaseCallbackTransactionManager bean is configured — AbstractCouchbaseConfiguration registers one automatically when extended:

@Service
public class BookingService {

    private final AirlineRepository airlines;
    private final FlightRepository flights;

    public BookingService(AirlineRepository airlines, FlightRepository flights) {
        this.airlines = airlines;
        this.flights = flights;
    }

    @Transactional
    public void bookSeat(String airlineId, String flightId) {
        Airline airline = airlines.findById(airlineId).orElseThrow();
        Flight flight = flights.findById(flightId).orElseThrow();
        flight.reserveSeat();
        flights.save(flight);
    }
}

Concurrency control, durability levels, and conflict handling for these transactions are Couchbase server concepts covered in Couchbase Reference: Concurrency, Locking and Durability, not repeated here.

Isolation level

CouchbaseCallbackTransactionManager accepts only Isolation.DEFAULT or Isolation.READ_COMMITTED on @Transactional; any stricter value throws IllegalArgumentException, because Couchbase distributed transactions always run at read-committed. They provide their own isolation model on top of that: writes are staged and become visible atomically at commit, and the transaction reads its own staged writes in the meantime, rather than the ANSI levels being offered as a dial. See Couchbase Reference: Concurrency, Locking and Durability for that model, and Transaction Isolation & Locking for how it compares with the relational stores and with @Version/CAS optimistic locking.

Summary

  • CouchbaseRepository gives CRUD plus derived N1QL/SQL++ finders from method names.

  • @Query supplies literal SQL++ with #\{#n1ql…​\} placeholders when derivation is insufficient.

  • @Scope/@Collection pin an entity to a specific keyspace within a bucket.

  • Full-text and vector search are reachable from repository methods or the SDK’s SearchQuery/vector APIs; see Couchbase Reference: Search, Analytics & Eventing for the index-design side.

  • CouchbaseTemplate (findByQuery, upsertById, removeById) and direct SDK access cover cases a repository is too high-level for.