If you’re used to JPA, you might feel lost when first approaching MongoDB — “Where do I even start?” The annotations and repositories look similar, but the concepts are subtly different.
This article targets Spring Boot 3.x (Java 17+) and walks you through connecting to MongoDB, from basic CRUD operations all the way to queries and aggregations.
Comparing MongoDB and RDBMS Concepts
First, let’s shift our mental model. MongoDB stores data in Collections rather than tables, and works with Documents (JSON format) instead of rows.
| RDBMS | MongoDB |
|---|---|
| Table | Collection |
| Row | Document |
| Primary Key | _id |
| Schema | Schema-less (flexible) |
Schema-less means that documents within the same collection can have completely different fields. This makes MongoDB well-suited for data with frequently changing schemas or deeply nested structures. Conversely, when you need complex transactions or strict data integrity, an RDBMS is the safer choice.
Setting Up a Local Environment with Docker Compose
You don’t need to install MongoDB locally — Docker Compose makes it easy to get up and running.
# docker-compose.yml
services:
mongo:
image: mongo:latest # For local development. In production, pin to a specific version (e.g., mongo:7.0)
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
volumes:
mongo_data:
Just run docker compose up -d. This is a simple setup with no authentication, but it’s sufficient for local development. For more on Docker-based setups, see Containerizing Spring Boot with Docker.
Adding the Dependency and Configuring the Connection
Add the following to your build.gradle:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}
And that’s all you need in application.properties:
spring.data.mongodb.uri=mongodb://localhost:27017/mydb
Spring Boot’s auto-configuration takes care of everything from creating the MongoClient to setting up the connection pool — just like it does with JPA.
Mapping Entity Classes to MongoDB with @Document
Use @Document instead of @Entity.
@Document(collection = "products")
public class Product {
@Id
private String id; // ObjectId can be received as a String type
private String name;
private int price;
@Field("category_name")
private String categoryName; // Customize the field name in the database
// Constructor, getters, and setters omitted
}
Nested objects can simply be added as fields of another class — they are automatically treated as embedded documents. No annotation like @Embedded is required.
Implementing CRUD Operations with MongoRepository
Just like JpaRepository, extending MongoRepository gives you basic CRUD out of the box.
public interface ProductRepository extends MongoRepository<Product, String> {
// save / findById / findAll / deleteById work as-is
}
Usage in the service layer is nearly identical to JPA as well.
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product create(Product product) {
return productRepository.save(product);
}
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
public void delete(String id) {
productRepository.deleteById(id);
}
}
For how to wire this up with a REST API, see Building a REST API with Spring Boot.
Writing Conditional Queries with Query Methods
You can use the exact same naming conventions as Spring Data JPA query methods.
public interface ProductRepository extends MongoRepository<Product, String> {
List<Product> findByName(String name);
List<Product> findByPriceLessThan(int price);
List<Product> findByNameAndCategoryName(String name, String categoryName);
@Query("{ 'price': { $gte: ?0, $lte: ?1 } }")
List<Product> findByPriceRange(int min, int max);
}
For a deeper look at JPA query method naming conventions, check out Spring Data JPA Query Methods Explained.
Writing Dynamic Queries with MongoTemplate
Query methods are convenient for static conditions, but for dynamic queries — such as “apply this filter only if the parameter is present” — MongoTemplate is easier to work with.
@Service
public class ProductSearchService {
private final MongoTemplate mongoTemplate;
public ProductSearchService(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
public List<Product> searchProducts(String name, Integer maxPrice) {
Query query = new Query();
if (name != null) {
query.addCriteria(Criteria.where("name").regex(name, "i"));
}
if (maxPrice != null) {
query.addCriteria(Criteria.where("price").lte(maxPrice));
}
return mongoTemplate.find(query, Product.class);
}
}
The pattern is to build conditions with Criteria.where("field").is(value) and add them to a Query object. Note that addCriteria() must be called on different fields each time. Calling it twice on the same field will throw an InvalidMongoDbApiUsageException.
Aggregating Data with the Aggregation Pipeline
MongoDB’s Aggregation Pipeline can also be expressed via the Spring Data API. Start by defining a class to hold the aggregation results:
public class CategorySummary {
@Id
private String id; // Holds the $group key (categoryName)
private int productCount;
private long totalPrice;
// Getters omitted
}
Then build and execute the pipeline:
public List<CategorySummary> aggregateByCategory() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("price").gt(0)), // $match
Aggregation.group("categoryName")
.count().as("productCount")
.sum("price").as("totalPrice"), // $group
Aggregation.sort(Sort.Direction.DESC, "productCount") // $sort
);
AggregationResults<CategorySummary> results =
mongoTemplate.aggregate(aggregation, "products", CategorySummary.class);
return results.getMappedResults();
}
MongoDB’s pipeline structure — filter with $match, aggregate with $group, sort with $sort — maps directly to Java code. Use AggregationResults#getMappedResults() to retrieve the results as a list of CategorySummary objects.
When to Use RDBMS vs. MongoDB
The right choice depends on the nature of your data and your requirements.
MongoDB is a good fit when:
- The schema changes frequently (e.g., configuration data, user attributes)
- The data is naturally nested (e.g., orders with line items, articles with comments)
- You need horizontal scaling for large datasets
RDBMS is a good fit when:
- You need complex transactions or strict data integrity
- Your data is normalized and involves complex JOINs
- You have a large existing codebase built on JPA
Using both RDBMS and MongoDB within a single system — choosing each based on the use case — is a realistic approach known as a “polyglot persistence” architecture. For a comparison with RDBMS-centric architectures, see Spring Boot: MyBatis vs. JPA Comparison.
Summary
Spring Data MongoDB is designed to feel familiar to JPA users, so the learning curve is relatively gentle if you already have JPA experience. In short, there are just five things you need to know to integrate MongoDB into a Spring Boot application:
- Define entities with
@Documentand@Id - Use
MongoRepositoryfor basic CRUD - Query methods follow the same naming conventions as JPA
- Use
MongoTemplatewithCriteriafor dynamic queries - Use
Aggregation.newAggregation()for aggregations
For testing, you can write integration tests using a MongoDB container with Testcontainers — see Integration Testing in Spring Boot with SpringBootTest and Testcontainers for details.