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

Learn why MongoDB was selected as a leader in the 2024 Gartner® Magic Quadrant™
MongoDB Developer
MongoDB
plus
Sign in to follow topics
MongoDB Developer Center
chevron-right
Developer Topics
chevron-right
Products
chevron-right
MongoDB
chevron-right

Understanding Embedded Types in MongoDB with Java and Helidon

Otavio Santana5 min read • Published Jan 09, 2025 • Updated Jan 09, 2025
MongoDBJava
Facebook Icontwitter iconlinkedin icon
java logo
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
MongoDB provides a flexible schema design, allowing data modeling patterns that differ significantly from traditional relational databases. With MongoDB, you model data specifically for your use cases rather than using a generic schema that attempts to cover all cases.
You can find all the code presented in this tutorial in the GitHub repository:
1git clone git@github.com:soujava/mongodb-embedding-sample.git

Prerequisites

For this tutorial, you’ll need:
You can use the following Docker command to start a standalone MongoDB instance:
1docker run --rm -d --name mongodb-instance -p 27017:27017 mongo
MongoDB people make a more efficient way of modeling data than what relational database users are used to. For one, it allows you to set your data as embedded types, subdocuments, and arrays with a more dynamic and flexible schema. This feature allows you to design a schema that best fits your application's requirements.
Subdocuments group related fields, reducing retrieval time and enhancing query accuracy. Arrays combine data from various tables, simplifying access.
Moreover, MongoDB's flexible schema allows for altering the design without distressing the existing ones. This feature is handy due to unforeseen progress and changes in the application scope, making MongoDB suitable for current-era data management.
While MongoDB has many strengths, it shares a common weakness with other database systems regarding data structuring. Optimizing a schema reduces the need for joins, improves performance, and simplifies development by minimizing relationships. Additionally, a well-structured schema can lower costs for storage, data retrieval, and command execution.
In this tutorial, you'll learn to use MongoDB with Helidon and Eclipse JNoSQL to manage products through a robust API. We'll create a Product entity that includes embedded types like Manufacturer and Category, as well as array fields for tags, all within a paginated RESTful API for efficient product management.
Begin by following the Helidon with MongoDB tutorial for the setup. After completing the setup, ensure you update the database name in the microprofile-config.properties file to reflect our example accurately.
1# Define the database name
2jnosql.document.database=products
With this foundation, we'll move on to implementing the Product entity and explore how MongoDB's embedded types can simplify data modeling for your application.

Step 1: Create the Product Entity

To start, we’ll define the core of our application: the Product entity. This class represents the product data structure and contains fields such as id, name, manufacturer, tags, and categories. The manufacturer field is modeled as a subdocument, while the tags field demonstrates how to use arrays in MongoDB. These fields showcase the flexibility of MongoDB’s schema design, allowing us to group related information and co-locate it efficiently.
In the src/main/java directory, create a Product class:
1import jakarta.json.bind.annotation.JsonbVisibility;
2import jakarta.nosql.Column;
3import jakarta.nosql.Convert;
4import jakarta.nosql.Entity;
5import jakarta.nosql.Id;
6import org.eclipse.jnosql.databases.mongodb.mapping.ObjectIdConverter;
7import org.soujava.samples.mongodb.products.infra.FieldVisibilityStrategy;
8import java.util.List;
9import java.util.Objects;
10import java.util.Set;
11
12@Entity
13@JsonbVisibility(FieldVisibilityStrategy.class)
14public class Product {
15 @Id
16 @Convert(ObjectIdConverter.class)
17 private String id;
18 @Column
19 private String name;
20 @Column
21 private Manufacturer manufacturer;
22 @Column
23 private List<String> tags;
24 @Column
25 private Set<Category> categories;
26}

Explanation of Annotations:

  • @Entity: Marks the Product class as a database entity for management by Jakarta NoSQL.
  • @JsonbVisibility (FieldVisibilityStrategy.class): Defines a visibility strategy for JSON binding, serializing by field instead of method, so getters and setters are unnecessary.
  • @Id: Indicates the primary identifier for the entity, uniquely distinguishing each document in the MongoDB collection.
  • @Convert (ObjectIdConverter.class): Converts the id field between Java strings and MongoDB's ObjectId format for compatibility.
  • @Column: Maps fields (name, manufacturer, tags, categories) for reading from or writing to MongoDB.

Step 2: Create Subdocuments

Manufacturer Subdocument
Next, we’ll define the Manufacturer subdocument. These embedded type groups detail the product’s manufacturer, such as name, address, and contactNumber. Using a subdocument, we can store this related information within the product, eliminating the need for a separate collection. For simplicity and immutability, we’ll implement this as a Java record.
Create a Manufacturer record in src/main/java:
1import jakarta.json.bind.annotation.JsonbVisibility;
2import jakarta.nosql.Column;
3import jakarta.nosql.Embeddable;
4import org.soujava.samples.mongodb.products.infra.FieldVisibilityStrategy;
5
6@Embeddable(Embeddable.EmbeddableType.GROUPING)
7@JsonbVisibility(FieldVisibilityStrategy.class)
8public record Manufacturer(
9 @Column String name,
10 @Column String address,
11 @Column String contactNumber
12) {}
Category Subdocument
After the manufacturer, we’ll create another embedded type: Category. This subdocument holds information about product categories, including their names and descriptions. Like the Manufacturer, Category is implemented as a Java record, keeping our model clean and immutable while enabling better query scope for related fields.
Create a Category record:
1import jakarta.nosql.Column;
2import jakarta.nosql.Embeddable;
3
4@Embeddable(Embeddable.EmbeddableType.GROUPING)
5public record Category(
6 @Column String name,
7 @Column String description
8) {}

Explanation of New Annotations:

  • @Embeddable: Specifies that the class is an embeddable type, meaning it is not stored as a separate collection but embedded within another document. The EmbeddableType.GROUPING option groups the annotated fields as a subdocument while the FLAT option works, as we know from the Jakarta Persistence, former JPA, thus, the fields will be appended as the Product’s field.

Step 3: Create the Repository Interface

With the entity and subdocuments in place, we need a way to interact with the database. Here, we define the ProductRepository interface, leveraging Jakarta Data for seamless CRUD operations. This repository bridges our code with the MongoDB database, simplifying data access and storage.
Define a ProductRepository to interact with MongoDB:
1import jakarta.data.repository.BasicRepository;
2import jakarta.data.repository.Repository;
3
4@Repository
5public interface ProductRepository extends BasicRepository<Product, String> {
6}

Step 4: Expose the Product API

We’ll create the ProductResource class to expose our data through a RESTful API. This resource handles HTTP requests, enabling clients to create, retrieve (with pagination), and delete products. Integrating the ProductRepository ensures efficient interaction with the MongoDB database while adhering to modern web standards.
Create a ProductResource class to handle HTTP requests:
1import jakarta.data.Order;
2import jakarta.data.Sort;
3import jakarta.data.page.PageRequest;
4import jakarta.enterprise.context.ApplicationScoped;
5import jakarta.inject.Inject;
6import jakarta.ws.rs.DELETE;
7import jakarta.ws.rs.DefaultValue;
8import jakarta.ws.rs.GET;
9import jakarta.ws.rs.POST;
10import jakarta.ws.rs.Path;
11import jakarta.ws.rs.PathParam;
12import jakarta.ws.rs.QueryParam;
13import jakarta.ws.rs.WebApplicationException;
14import jakarta.ws.rs.core.Response;
15import java.util.List;
16import java.util.logging.Logger;
17
18@ApplicationScoped
19@Path("/products")
20public class ProductResource {
21 private static final Order<Product> ORDER = Order.by(Sort.asc("name"));
22 private static final Logger LOGGER = Logger.getLogger(ProductResource.class.getName());
23 private final ProductRepository repository;
24
25 @Inject
26 public ProductResource(ProductRepository repository) {
27 this.repository = repository;
28 }
29
30 @Deprecated
31 public ProductResource() {
32 this(null);
33 }
34
35 @GET
36 public List<Product> get(
37 @QueryParam("page") @DefaultValue("1") int page,
38 @QueryParam("size") @DefaultValue("10") int size) {
39 LOGGER.info("Fetching page: " + page + ", size: " + size);
40 var request = PageRequest.ofPage(page).size(size);
41 return repository.findAll(request, ORDER).content();
42 }
43
44 @POST
45 public Product insert(Product product) {
46 LOGGER.info("Saving product: " + product);
47 return repository.save(product);
48 }
49
50 @DELETE
51 @Path("{id}")
52 public void delete(@PathParam("id") String id) {
53 repository.deleteById(id);
54 }
55
56 @GET
57 @Path("{id}")
58 public Product findById(@PathParam("id") String id) {
59 return repository.findById(id)
60 .orElseThrow(() -> new WebApplicationException("Product not found with ID: " + id, Response.Status.NOT_FOUND));
61 }
62}

Step 5: Build and Run the Application

Finally, it’s time to integrate everything and run the application. After packaging the project with Maven, start the application. Ensure MongoDB is running, either locally or via MongoDB Atlas. Once the application is up and running, you can test the API endpoints to interact with the Product data.
Make sure MongoDB is running (locally or on MongoDB Atlas). Then, build and run the application:
1mvn package
2java -jar target/embedded-mongodb.jar

Step 6: Test the API

Create a Product
1curl -X POST -H "Content-Type: application/json" -d '{
2 "name": "Smartphone",
3 "manufacturer": {
4 "name": "Tech Co",
5 "address": "1234 Tech Street",
6 "contactNumber": "+123456789"
7 },
8 "tags": ["mobile", "smart"],
9 "categories": [
10 {"name": "Electronics", "description": "Devices and gadgets." },
11 {"name": "Gadgets", "description": "Small useful tools and devices."}
12 ]
13}' http://localhost:8080/products
Get All Products
1curl -X GET http://localhost:8080/products?page=1&size=10
Get a Product by ID
1curl -X GET http://localhost:8080/products/{id}
Delete a Product by ID
1curl -X DELETE http://localhost:8080/products/{id}

Conclusion

MongoDB's embedded types simplify relationships compared to relational databases. You can reduce joins and improve performance by grouping related data as subdocuments and arrays. Using Helidon, Jakarta Data, and Eclipse JNoSQL, you can easily create modern, scalable Java applications with MongoDB.
Ready to explore the benefits of MongoDB Atlas? Get started now by trying MongoDB Atlas.
Access the source code used in this tutorial.
Any questions? Come chat with us in the MongoDB Community Forum.
References:
Top Comments in Forums
There are no comments on this article yet.
Start the Conversation

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Article

Using MongoDB With Rust Web Development Framework


Aug 29, 2024 | 1 min read
Article

Wordle Solving Using MongoDB Query API Operators


Feb 01, 2023 | 4 min read
Tutorial

Orchestrating MongoDB & BigQuery for ML Excellence with PyMongoArrow and BigQuery Pandas Libraries


Feb 08, 2024 | 4 min read
Tutorial

Modernize Your Insurance Data Models With MongoDB Relational Migrator


Jun 03, 2024 | 14 min read
Table of Contents