Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Join us at AWS re:Invent 2024! Learn how to use MongoDB for AI use cases.
MongoDB Developer
Java
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
Javachevron-right

How to Build a CRUD Application With MongoDB, Quarkus, and GraalVM

Maxime Beugnet7 min read • Published Aug 29, 2024 • Updated Aug 29, 2024
QuarkusDockerMongoDBJava
FULL APPLICATION
Facebook Icontwitter iconlinkedin icon
Rate this quickstart
star-empty
star-empty
star-empty
star-empty
star-empty

What is Quarkus?

When we write a traditional Java application, our Java source code is compiled and transformed into Java bytecode. This bytecode can then be executed by a Java virtual machine (JVM) specific to the operating system you are running. This is why we can say that Java is a portable language. You compile once, and you can run it everywhere, as long as you have the right JVM on the right machine.
This is a great mechanism, but it comes at a cost. Starting a program is slow because the JVM and the entire context need to be loaded first before running anything. It's not memory-efficient because we need to load hundreds of classes that might not be used at all in the end as the classpath scanning only occurs after.
This was perfectly fine in the old monolithic realm, but this is totally unacceptable in the new world made of lambda functions, cloud, containers, and Kubernetes. In this context, a low memory footprint and a lightning-fast startup time are absolutely mandatory.
This is where Quarkus comes in. Quarkus is a Kubernetes-native Java framework tailored for GraalVM and HotSpot.
With Quarkus, you can build native binaries that can boot and send their first response in 0.042 seconds versus 9.5 seconds for a traditional Java application.
Quarkus startup time
In this tutorial, we are going to build a Quarkus application that can manage a persons collection in MongoDB. The goal is to perform four simple CRUD operations with a REST API using a native application.

Prerequisites

For this tutorial, you'll need:
If you don't want to code along and prefer to check out directly the final code:
1git clone git@github.com:mongodb-developer/quarkus-mongodb-crud.git

How to set up Quarkus with MongoDB

TL;DR: Use this link and click on generate your application or clone the GitHub repository.
The easiest way to get your project up and running with Quarkus and all the dependencies you need is to use https://code.quarkus.io/.
Similar to Spring initializr, the Quarkus project starter website will help you select your dependencies and build your Maven or Gradle configuration file. Some dependencies will also include a starter code to assist you in your first steps.
For our project, we are going to need:
  • MongoDB client [quarkus-mongodb-client].
  • SmallRye OpenAPI [quarkus-smallrye-openapi].
  • REST [quarkus-rest].
  • REST Jackson [quarkus-rest-jackson].
Feel free to use the group and artifact of your choice. Make sure the Java version matches the version of your GraalVM version, and we are ready to go.
Download the zip file and unzip it in your favorite project folder. Once it's done, take some time to read the README.md file provided.
Finally, we need a MongoDB cluster. Two solutions:
  • Create a new cluster on MongoDB Atlas and retrieve the connection string, or
  • Create an ephemeral single-node replica set with Docker.
1docker run --rm -d -p 27017:27017 -h $(hostname) --name mongo mongo:latest --replSet=RS && sleep 5 && docker exec mongo mongosh --quiet --eval "rs.initiate();"
Either way, the next step is to set up your connection string in the application.properties file.
1quarkus.mongodb.connection-string=mongodb://localhost:27017

CRUD operations in Quarkus with MongoDB

Now that our Quarkus project is ready, we can start developing.
First, we can start the developer mode which includes live coding (automatic refresh) without the need to restart the program.
1./mvnw compile quarkus:dev
The developer mode comes with two handy features:
Feel free to take some time to explore both these UIs and see the capabilities they offer.
Also, as your service is now running, you should be able to receive your first HTTP communication. Open a new terminal and execute the following query:
1curl http://localhost:8080/hello
Note: If you cloned the repo, then it’s /api/hello. We are changing this below in a minute.
Result:
1Hello from Quarkus REST
This works because your project currently contains a single class GreetingResource.java with the following code.
1package com.mongodb;
2
3import jakarta.ws.rs.GET;
4import jakarta.ws.rs.Path;
5import jakarta.ws.rs.Produces;
6import jakarta.ws.rs.core.MediaType;
7
8@Path("/hello")
9public class GreetingResource {
10
11 @GET
12 @Produces(MediaType.TEXT_PLAIN)
13 public String hello() {
14 return "Hello from Quarkus REST";
15 }
16}

PersonEntity

"Hello from Quarkus REST" is nice, but it's not our goal! We want to manipulate data from a persons collection in MongoDB.
Let's create a classic PersonEntity.java POJO class. I created it in the default com.mongodb package which is my group from earlier. Feel free to change it.
1package com.mongodb;
2
3import com.fasterxml.jackson.databind.annotation.JsonSerialize;
4import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
5import org.bson.types.ObjectId;
6
7import java.util.Objects;
8
9public class PersonEntity {
10
11 @JsonSerialize(using = ToStringSerializer.class)
12 public ObjectId id;
13 public String name;
14 public Integer age;
15
16 public PersonEntity() {
17 }
18
19 public PersonEntity(ObjectId id, String name, Integer age) {
20 this.id = id;
21 this.name = name;
22 this.age = age;
23 }
24
25 @Override
26 public int hashCode() {
27 int result = id != null ? id.hashCode() : 0;
28 result = 31 * result + (name != null ? name.hashCode() : 0);
29 result = 31 * result + (age != null ? age.hashCode() : 0);
30 return result;
31 }
32
33 @Override
34 public boolean equals(Object o) {
35 if (this == o) return true;
36 if (o == null || getClass() != o.getClass()) return false;
37
38 PersonEntity that = (PersonEntity) o;
39
40 if (!Objects.equals(id, that.id)) return false;
41 if (!Objects.equals(name, that.name)) return false;
42 return Objects.equals(age, that.age);
43 }
44
45 public ObjectId getId() {
46 return id;
47 }
48
49 public void setId(ObjectId id) {
50 this.id = id;
51 }
52
53 public String getName() {
54 return name;
55 }
56
57 public void setName(String name) {
58 this.name = name;
59 }
60
61 public Integer getAge() {
62 return age;
63 }
64
65 public void setAge(Integer age) {
66 this.age = age;
67 }
68}
We now have a class to map our MongoDB documents to using Jackson.

PersonRepository

Now that we have a PersonEntity, we can create a PersonRepository template, ready to welcome our CRUD queries.
Create a PersonRepository.java class next to the PersonEntity.java one.
1package com.mongodb;
2
3import com.mongodb.client.MongoClient;
4import com.mongodb.client.MongoCollection;
5import jakarta.enterprise.context.ApplicationScoped;
6
7@ApplicationScoped
8public class PersonRepository {
9
10 private final MongoClient mongoClient;
11 private final MongoCollection<PersonEntity> coll;
12
13 public PersonRepository(MongoClient mongoClient) {
14 this.mongoClient = mongoClient;
15 this.coll = mongoClient.getDatabase("test").getCollection("persons", PersonEntity.class);
16 }
17
18 // CRUD methods will go here
19
20}

PersonResource

We are now almost ready to create our first CRUD method. Let's update the default GreetingResource.java class to match our goal.
  1. Rename the file GreetingResource.java to PersonResource.java.
  2. In the test folder, also rename the default test files to PersonResourceIT.java and PersonResourceTest.java.
  3. Update PersonResource.java like this:
1package com.mongodb;
2
3import jakarta.inject.Inject;
4import jakarta.ws.rs.*;
5import jakarta.ws.rs.core.MediaType;
6
7@Path("/api")
8@Consumes(MediaType.APPLICATION_JSON)
9@Produces(MediaType.APPLICATION_JSON)
10public class PersonResource {
11
12 @Inject
13 PersonRepository personRepository;
14
15 @GET
16 @Path("/hello")
17 public String hello() {
18 return "Hello from Quarkus REST";
19 }
20
21 // CRUD routes will go here
22
23}
Note that with the @Path("/api") annotation, the URL of our /hello service is now /api/hello.
As a consequence, update PersonResourceTest.java so our test keeps working.
1package com.mongodb;
2
3import io.quarkus.test.junit.QuarkusTest;
4import org.junit.jupiter.api.Test;
5
6import static io.restassured.RestAssured.given;
7import static org.hamcrest.CoreMatchers.is;
8
9@QuarkusTest
10class PersonResourceTest {
11 @Test
12 void testHelloEndpoint() {
13 given().when().get("/api/hello").then().statusCode(200).body(is("Hello from Quarkus REST"));
14 }
15}

Create a person

All the code blocks are now in place. We can create our first route to be able to create a new person.
In the repository, add the following method that inserts a PersonEntity and returns the inserted document's ObjectId in String format.
1public String add(PersonEntity person) {
2 return coll.insertOne(person).getInsertedId().asObjectId().getValue().toHexString();
3}
In the resource file, we can create the corresponding route:
1@POST
2@Path("/person")
3public String createPerson(PersonEntity person) {
4 return personRepository.add(person);
5}
Without restarting the project (remember the dev mode?), you should be able to test this route.
1curl -X POST http://localhost:8080/api/person \
2 -H 'Content-Type: application/json' \
3 -d '{"name": "John Doe", "age": 30}'
This should return the ObjectId of the new person document.
1661dccf785cd323349ca42f7
If you connect to the MongoDB instance with mongosh, you can confirm that the document made it:
1RS [direct: primary] test> db.persons.find()
2[
3 {
4 _id: ObjectId('661dccf785cd323349ca42f7'),
5 age: 30,
6 name: 'John Doe'
7 }
8]

Read persons

Now, we can read all the persons in the database, for example.
In the repository, add:
1public List<PersonEntity> getPersons() {
2 return coll.find().into(new ArrayList<>());
3}
In the resource, add:
1@GET
2@Path("/persons")
3public List<PersonEntity> getPersons() {
4 return personRepository.getPersons();
5}
Now, we can retrieve all the persons in our database:
1curl http://localhost:8080/api/persons
This returns a list of persons:
1[
2 {
3 "id": "661dccf785cd323349ca42f7",
4 "name": "John Doe",
5 "age": 30
6 }
7]

Update person

It's John Doe's anniversary! Let's increment his age by one.
In the repository, add:
1public long anniversaryPerson(String id) {
2 Bson filter = eq("_id", new ObjectId(id));
3 Bson update = inc("age", 1);
4 return coll.updateOne(filter, update).getModifiedCount();
5}
In the resource, add:
1@PUT
2@Path("/person/{id}")
3public long anniversaryPerson(@PathParam("id") String id) {
4 return personRepository.anniversaryPerson(id);
5}
Time to test this party:
1curl -X PUT http://localhost:8080/api/person/661dccf785cd323349ca42f7
This returns 1 which is the number of modified document(s). If the provided ObjectId doesn't match a person's id, then it returns 0 and MongoDB doesn't perform any update.

Delete person

Finally, it's time to delete John Doe...
In the repository, add:
1public long deletePerson(String id) {
2 Bson filter = eq("_id", new ObjectId(id));
3 return coll.deleteOne(filter).getDeletedCount();
4}
In the resource, add:
1@DELETE
2@Path("/person/{id}")
3public long deletePerson(@PathParam("id") String id) {
4 return personRepository.deletePerson(id);
5}
Let's test:
1curl -X DELETE http://localhost:8080/api/person/661dccf785cd323349ca42f7
Again, it returns 1 which is the number of deleted document(s).
Now that we have a working Quarkus application with a MongoDB CRUD service, it's time to experience the full power of Quarkus.

Quarkus native build

Quit the developer mode by simply hitting the q key in the relevant terminal.
It's time to build the native executable that we can use in production with GraalVM and experience the insanely fast start-up time.
Use this command line to build directly with your local GraalVM and other dependencies.
1./mvnw package -Dnative
Or use the Docker image that contains everything you need:
1./mvnw package -Dnative -Dquarkus.native.container-build=true
The final result is a native application, ready to be launched, in your target folder.
1./target/quarkus-mongodb-crud-1.0.0-SNAPSHOT-runner
On my laptop, it starts in just 0.019s! Remember how much time Spring Boot needs to start an application and respond to queries for the first time?!
You can read more about how Quarkus makes this miracle a reality in the container first documentation.

Conclusion

In this tutorial, we've explored how Quarkus and MongoDB can team up to create a lightning-fast RESTful API with CRUD capabilities.
Now equipped with these insights, you're ready to build blazing-fast APIs with Quarkus, GraalVM, and MongoDB. Dive into the provided GitHub repository for more details.
If you have questions, please head to our Developer Community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB.
Top Comments in Forums
There are no comments on this article yet.
Start the Conversation

Facebook Icontwitter iconlinkedin icon
Rate this quickstart
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Quickstart

Java - Change Streams


Oct 01, 2024 | 10 min read
Quickstart

Introduction to MongoDB and Helidon


Nov 12, 2024 | 6 min read
Quickstart

Java - Mapping POJOs


Mar 01, 2024 | 5 min read
Quickstart

Java - MongoDB Multi-Document ACID Transactions


Mar 01, 2024 | 10 min read
Table of Contents