rgoussu@goussu: ~/library/java/storage
~/library/java/storage cat document-and-nosql.md

Document and NoSQL stores from Java

# MongoDB, Cassandra, Couchbase, and DynamoDB access layers per framework, and how document modeling and tunable consistency surface in the client.

Conceptsaved 2026-08-09 #java#storage#nosql#mongodb#cassandra

Overview

There is no JDBC for NoSQL: each store ships its own driver, and the framework layers (Spring Data, Panache, Micronaut Data) paper a repository idiom over fundamentally different data models. The Java-visible differences are the point — MongoDB's POJO codecs and tunable read/write concerns, Cassandra's prepared statements and per-query consistency levels, DynamoDB's enhanced client mapping. The hardest part for JPA-trained hands is unlearning normalized modeling: these stores reward designing documents and partitions around queries.

Key points

  • MongoDB driver: mongodb-driver-sync and mongodb-driver-reactivestreams share a core; POJO codecs map classes to BSON without an ORM lifecycle — no dirty checking, no lazy proxies, what you save is what you wrote.
  • Spring Data MongoDB: MongoTemplate + repositories with derived queries, @Document mapping, and reactive twins (ReactiveMongoTemplate) over the reactive driver.
  • Quarkus: MongoDB with Panache gives the active-record/repository idiom (PanacheMongoEntity) over the vendor driver. Micronaut Data MongoDB compiles the repository queries at build time, consistent with its JDBC/JPA story.
  • Cassandra: DataStax java-driver (now the Apache Cassandra Java driver) — session-based, async-first, with a mapper via annotation processing; Spring Data Cassandra layers CassandraTemplate and repositories on top.
  • Prepared statements are mandatory in Cassandra: prepare once, bind many — they carry token-aware routing metadata; re-preparing per call is a classic anti-pattern the driver logs about.
  • Couchbase: official Java SDK (sync/async/reactive) + Spring Data Couchbase; N1QL gives a SQL-ish query surface over JSON documents.
  • DynamoDB: AWS SDK v2 enhanced client (DynamoDbEnhancedClient) maps annotated beans (@DynamoDbBean) to items — the closest thing to an official mapper; single-table design pushes even harder against relational instincts.
  • Consistency is a client-side dial: Mongo read/write concerns, Cassandra consistency levels, Dynamo's ConsistentRead flag — the application chooses per operation what the RDBMS decided for you globally.

Details

Access layers per store

Store Driver Mapping Spring Quarkus Micronaut
MongoDB mongodb-driver-sync / -reactivestreams POJO codecs Spring Data MongoDB MongoDB with Panache Micronaut Data MongoDB
Cassandra Apache Cassandra java-driver driver object mapper Spring Data Cassandra Cassandra client (quarkiverse) Micronaut Cassandra
Couchbase Couchbase Java SDK SDK JSON mapping Spring Data Couchbase Couchbase (quarkiverse) — (community)
DynamoDB AWS SDK v2 Enhanced client (@DynamoDbBean) Spring Cloud AWS Quarkiverse Amazon DynamoDB Micronaut AWS SDK

Modeling against JPA instincts

  • Embed, don't join: a Mongo order document carries its lines inline; "fetch strategy" stops being a concept. Duplicate data deliberately; update paths, not normal forms, drive the design.
  • Cassandra models queries, not entities: one table per query pattern, partition key chosen for even distribution and bounded partition size; ALLOW FILTERING in production code is a design failure, not a hint.
  • No cascade, no orphan removal, no transactions by default: multi-document transactions exist in MongoDB (replica sets) but cost enough that document design should mostly avoid needing them; Cassandra offers only lightweight transactions (Paxos) per partition.

Consistency surfacing in the client

  • MongoDB: WriteConcern.MAJORITY vs W1, ReadConcern/ReadPreference (primary vs secondary reads) — set per operation, collection, or client.
  • Cassandra: ConsistencyLevel per statement (LOCAL_QUORUM the usual production default); R + W > RF for read-your-writes.

Examples

// Cassandra: prepare once, bind per call
PreparedStatement byId = session.prepare(
    "SELECT * FROM orders_by_customer WHERE customer_id = ?");
ResultSet rs = session.execute(
    byId.bind(customerId).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM));
// Spring Data MongoDB repository — same idiom, different substrate
public interface OrderRepository extends MongoRepository<Order, String> {
    List<Order> findByCustomerIdAndStatus(String customerId, Status status);
}

Related