Jakarta NoSQL in Jakarta EE 12 M2: A Maturing Story of Polyglot Persistence
Jakarta NoSQL in Jakarta EE 12 strengthens polyglot persistence with JCQL, fluent queries, projections, and richer APIs for unified Java–NoSQL integration.
Join the DZone community and get the full member experience.
Join For FreeNoSQL databases did not become popular because relational databases failed; relational databases are still alive. They became popular because systems changed.
As applications grew more distributed, data volumes increased, and access patterns diversified, the limits of a single persistence model became more visible. Document databases simplified aggregate storage, key-value stores optimized for latency and scale, column databases handled massive datasets efficiently, and graph databases modeled relationships that relational schemas struggled to express. Over time, these technologies moved from experimentation into critical, production-grade use cases, including highly regulated industries such as finance.
With that consolidation came an unavoidable requirement: first-class integration. And for enterprise systems written in Java, that integration needed to feel native.
Jakarta NoSQL
The rise of NoSQL coincided with the growing adoption of polyglot persistence — the idea that different parts of a system should use different data stores depending on context. Yet for many years, enterprise Java offered little guidance or standardization in this space. Persistence APIs remained tightly coupled to relational assumptions, even as real-world architectures moved in a different direction.
After years of discussion, experimentation, and inevitable delays, Jakarta NoSQL was born with a clear goal: to provide a standard way to integrate Java applications with NoSQL databases without forcing them into a relational mold.
Jakarta NoSQL is not a database, nor is it a one-size-fits-all abstraction. It is a framework and specification that simplifies how Java applications interact with a wide range of NoSQL data stores by providing a unified API, consistent annotations, and multiple interaction styles. The emphasis is on productivity and clarity, while preserving the flexibility required by fundamentally different database models.
At its core, Jakarta NoSQL aims to reduce the friction developers experience when working with non-relational databases. It does so by focusing on common operations that appear across NoSQL systems — mapping, querying, inserting, updating, and deleting — while avoiding assumptions that only make sense in relational environments.
Several design goals shape the specification. Jakarta NoSQL focuses on increasing productivity for common NoSQL operations, integrating rich object mapping directly into the API, and offering both Java-based query APIs and fluent-style builders. It is designed to work across multiple NoSQL database types and to adapt quickly to new behaviors through extensions. Where it makes sense, it deliberately reuses familiar naming conventions from Jakarta Persistence to reduce cognitive load.
Jakarta NoSQL defines one API per NoSQL database category, but it relies on the same mapping annotations developers already know from Jakarta Persistence and JPA. As a result, a single annotated domain model can work across more than twenty NoSQL databases with minimal changes.
@Entity
public class Car {
@Id
private Long id;
@Column
private String name;
@Column
private CarType type;
}
The annotations look familiar by design. @Entity, @Id, and @Column mean essentially what developers expect them to mean, even though the underlying storage model is not relational. Jakarta NoSQL also supports richer mapping scenarios using annotations such as @MappedSuperclass, @Embeddable, inheritance mappings, converters, and projections.
This alignment significantly lowers the barrier to entry. Java developers can apply existing mental models while targeting very different persistence technologies.
The central entry point for interacting with NoSQL databases in Jakarta NoSQL is the Template interface. The Template provides a consistent, high-level API for performing common persistence operations and supports multiple interaction styles depending on the use case.
@Inject
Template template;
Car ferrari = Car.builder()
.id(1L)
.name("Ferrari")
.type(CarType.SPORT)
.build();
template.insert(ferrari);
Optional<Car> car = template.find(Car.class, 1L);
template.delete(Car.class, 1L);
This API supports basic CRUD operations, fluent-style query builders, and string-based queries with support for projections. Developers can choose the most expressive or appropriate style for each scenario without switching libraries or programming models.
Fluent APIs and Query Options in Jakarta NoSQL
For cases where queries need to be constructed dynamically, Jakarta NoSQL provides a fluent API that allows expressive filtering and ordering without resorting to string-based queries.
List<Car> sportsCars = template.select(Car.class)
.where("type").eq(CarType.SPORT)
.orderBy("name")
.result();
template.delete(Car.class)
.where("type").eq(CarType.CLASSIC)
.execute();
This style is particularly useful when the query structure depends on runtime conditions, while still remaining readable and type-aware.
Jakarta EE 12 represents an important milestone for Jakarta NoSQL, as it deepens its integration with Jakarta Query. Jakarta NoSQL now supports string-based queries written in the Jakarta Common Query Language (JCQL), a query language designed to work across both relational and non-relational data stores.
JCQL is deliberately defined as a constrained subset of JPQL. The goal is not to expose every relational feature, but to provide a common, portable query model that can be implemented consistently across diverse databases.
Jakarta NoSQL exposes two primary ways to execute JCQL queries through the Template.
The query(String) method executes a generic query that always returns entities and requires an explicit FROM clause.
List<Car> cars = template.query("FROM Car WHERE type = :type")
.bind("type", CarType.SPORT)
.result();
Optional<Car> one = template.query("FROM Car WHERE id = :id")
.bind("id", 42)
.singleResult();
For more advanced use cases, the typedQuery(String, Class<T>) method allows mapping results directly to entities or projections. This is especially powerful when combined with Java records and the @Projection annotation.
@Projection
public record TechCarView(String name, CarType type) {}
List<TechCarView> cars = template
.typedQuery("FROM Car WHERE type = 'SPORT'", TechCarView.class)
.result();
When a projection is annotated with @Projection(from = …), the FROM clause can be omitted entirely, further reducing boilerplate.
@Projection(from = Car.class)
public record BudgetCar(String name, double price) {}
List<BudgetCar> cheapCars = template
.typedQuery("WHERE price < 100", BudgetCar.class)
.result();
Both query styles support retrieving a single result wrapped in an Optional, reinforcing a modern, null-safe programming model.
Optional<Car> car = template.query("FROM Car WHERE id = :id")
.bind("id", "c-001")
.singleResult();
Optional<BudgetCar> result = template
.typedQuery("WHERE price < 100", BudgetCar.class)
.singleResult();
Beyond query expressiveness, Jakarta NoSQL’s fluent API has also expanded its capabilities in Jakarta EE 12. New operations such as contains, startsWith, endsWith, and count make it easier to express common filtering and aggregation scenarios in a database-agnostic way.
long count = template.query("FROM Car WHERE type = :type")
.bind("type", CarType.SPORT)
.count();
Taken together, these improvements reflect a clear direction. Jakarta NoSQL is no longer just about basic mapping and CRUD operations. It is evolving into a mature, expressive data access layer that integrates cleanly with the rest of the Jakarta EE platform.
Jakarta NoSQL does not attempt to hide the differences between NoSQL databases, nor does it force them into relational abstractions. Instead, it focuses on what these databases have in common and provides Java developers with a consistent, productive way to work with them. With Jakarta EE 12, that vision becomes clearer: polyglot persistence is no longer an architectural afterthought, but a first-class concern in enterprise Java.
Jakarta NoSQL’s evolution mirrors the broader evolution of Jakarta EE itself — incremental, pragmatic, and focused on real-world needs rather than theoretical purity. For teams building modern, data-intensive systems, it represents a meaningful step toward making NoSQL a natural part of the enterprise Java toolbox.
Opinions expressed by DZone contributors are their own.
Comments