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-syncandmongodb-driver-reactivestreamsshare 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,@Documentmapping, 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 layersCassandraTemplateand 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
ConsistentReadflag — 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 FILTERINGin 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.MAJORITYvsW1,ReadConcern/ReadPreference(primary vs secondary reads) — set per operation, collection, or client. - Cassandra:
ConsistencyLevelper statement (LOCAL_QUORUMthe 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
- Storage access from Java — the map — parent overview by storage kind.
- Relational databases from Java — the JPA habits this note tells you to unlearn.
- Databases and other storage systems — the replication and consistency machinery these dials control.