From SQL to NoSQL Without the Chaos: Building Modern Java Apps with Quarkus and Jakarta NoSQL
Discover how Jakarta NoSQL brings standardization to the NoSQL world—and why Quarkus is the perfect engine to power your next cloud-native Java application.
Data persistence in Java has long been centered around relational databases, supported by battle-tested tools like JDBC and JPA. But the needs of modern applications have evolved. The explosion of big data, the rise of artificial intelligence, and the demands for flexible, schema-less storage brought NoSQL databases into the spotlight. From document stores and key-value caches to column-family and graph-based systems, NoSQL databases offer performance and scalability benefits, but at the cost of standardization.
Each database introduced its own Java API or driver. Developers had to learn different models and approaches for MongoDB, Redis, Cassandra, and others. Switching from one to another often meant rewriting significant parts of the data access layer. Jakarta NoSQL was created to solve this fragmentation. Much like JPA abstracted the relational world, Jakarta NoSQL aims to provide a common programming model for the diverse NoSQL ecosystem.
Parallel to this evolution in data persistence, Java itself has been transforming. Quarkus is a new breed of Java framework optimized for cloud-native applications. Designed for fast startup times, low memory usage, and smooth integration with GraalVM, Quarkus brings a modern developer experience while remaining true to Java's strengths. When Jakarta NoSQL and Quarkus are combined, developers get the best of both worlds: a unified, portable API for NoSQL data access, and a high-performance runtime built for microservices and serverless.
What’s behind Jakarta NoSQL
Jakarta NoSQL introduces two complementary layers. The Communication API gives direct access to NoSQL database operations through interfaces for key-value, document, column, and graph databases. This layer retains the database's conceptual model while removing vendor-specific dependencies. The Mapping API builds on this by allowing developers to work with POJOs, using familiar annotations like @Entity
, @Id
, and @Column
to map Java objects to database structures.
The Mapping API supports repositories similar to those in Spring Data or Jakarta Data. Developers can declare interfaces like CrudRepository<Person, String>
and get methods like save
, findById
, and findAll
without writing implementation code. Derived queries allow methods like findByLastName(String name)
to be automatically translated into database queries.
This dual-layer approach provides flexibility. For rapid development, the Mapping API and repositories are ideal. For fine-tuned queries or access to specific database features, the Communication API with Templates offers full control. The developer chooses the right level of abstraction.
When Jakarta NoSQL Shines
Jakarta NoSQL fits naturally into microservice architectures, where different services may use different types of databases. A recommendation service might use a graph database, while a product catalog relies on a document store. With Jakarta NoSQL, each service can use its optimal data model while keeping a consistent Java API across the codebase.
In polyglot persistence scenarios, where applications use multiple types of databases simultaneously, Jakarta NoSQL helps unify the developer experience. It also helps with database migrations: starting with MongoDB and later switching to Couchbase becomes more manageable when the data access code is built on standard APIs.
For teams transitioning from relational databases, the learning curve is eased. Developers already comfortable with JPA and Jakarta EE can adapt quickly, thanks to the shared annotation-based model. Prototyping also becomes smoother. With Jakarta NoSQL, developers can start building an app without committing to a specific NoSQL backend.
Why Quarkus Complements Jakarta NoSQL
Quarkus brings the performance and developer experience needed for modern workloads. It does much of its configuration and setup at build time, resulting in smaller runtime footprints and lightning-fast startups. This is a key advantage in containerized environments, where fast scaling and low resource use are critical.
With features like live coding, unified configuration, and out-of-the-box support for both imperative and reactive programming, Quarkus enhances developer productivity. It also integrates cleanly with Jakarta standards and supports ahead-of-time compilation to native executables using GraalVM. This makes Jakarta NoSQL applications written in Quarkus particularly well-suited for cloud-native deployment.
The quarkus-jnosql
extensions bridge Jakarta NoSQL with the Quarkus ecosystem. These extensions handle configuration, dependency injection, and lifecycle management so developers can focus on writing business logic.
A Hands-On Example Using MongoDB
To see Jakarta NoSQL and Quarkus in action, let’s build a simple REST API that stores and retrieves person records from MongoDB.
First, generate a new project with the necessary extensions:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=quarkus-jnosql-example \
-Dextensions="resteasy-jackson, quarkus-jnosql-mapping-document-mongodb"
cd quarkus-jnosql-example
Then configure your database connection in application.properties
:
quarkus.jnosql.document.provider=mongodb
# Optional, if you are not using DevServices configure where Mongo runs
#quarkus.jnosql.document.mongodb.hosts=localhost:27017
#quarkus.jnosql.document.database=peopleDB
Define your entity:
@Entity
public class Person {
@Id
private String id;
@Column
private String firstName;
@Column("lastName")
private String surName;
@Column
private int age;
}
Then create a repository interface:
@Repository
public interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findBySurName(String surName);
List<Person> findByAgeGreaterThan(int age);
}
Now expose a REST API:
@Path("/persons")
@ApplicationScoped
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class PersonResource {
@Inject
PersonRepository personRepository;
@GET
public List<Person> getAll() {
return personRepository.findAll().toList();
}
@POST
public Response create(Person person) {
person.setId(null);
Person saved = personRepository.save(person);
return Response.status(201).entity(saved).build();
}
@GET
@Path("/{id}")
public Response getById(@PathParam("id") String id) {
return personRepository.findById(id)
.map(Response::ok)
.orElse(Response.status(404))
.build();
}
}
Run your app with ./mvnw quarkus:dev
and test your endpoints.
Using Templates for Custom Access
Sometimes repositories aren’t flexible enough. The Communication API’s templates let you build queries dynamically. Here’s how to use DocumentTemplate
to fetch people older than a certain age:
@ApplicationScoped
public class PersonService {
@Inject
DocumentTemplate template;
public List<Person> findOlderThan(int age) {
DocumentQuery query = select()
.from("Person")
.where("age").gt(age)
.build();
return template.select(query).getResultList();
}
}
You can wire this into a resource just like a repository. Templates give you more control while still using Jakarta NoSQL’s standard abstractions.
Working with KeyValueTemplate and ColumnTemplate
Beyond document databases, Jakarta NoSQL also supports other NoSQL models. The KeyValueTemplate
is great for simple key-value operations like caching or session storage. For example:
@ApplicationScoped
public class ConfigService {
@Inject
KeyValueTemplate template;
public void saveSetting(String key, String value) {
template.put(key, value);
}
public String getSetting(String key) {
return template.get(key, String.class).orElse("default");
}
}
The ColumnTemplate
works with column-family databases like Cassandra. You can retrieve rows dynamically:
@ApplicationScoped
public class AnalyticsService {
@Inject
ColumnTemplate template;
public List<ColumnEntity> findAllEvents(String eventType) {
ColumnQuery query = select()
.from("events")
.where("type").eq(eventType)
.build();
return template.select(query).getResultList();
}
}
These templates let you interact directly with the underlying database structure while maintaining portability.
Final Thoughts
Jakarta NoSQL and Quarkus form a powerful combination for building cloud-native Java applications. Jakarta NoSQL removes the fragmentation of NoSQL APIs, letting developers work with familiar Java patterns across different databases. Quarkus provides the runtime performance, cloud-native integration, and developer experience that modern projects demand.
By combining the two, you gain the ability to choose the best NoSQL database for the job while keeping your code portable, maintainable, and efficient. Whether you're building microservices, APIs, or data-driven applications, Jakarta NoSQL with Quarkus is a future-ready foundation for scalable and elegant Java development.
Further reading: