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.

RDBMSMongoDB
TableCollection
RowDocument
Primary Key_id
SchemaSchema-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 @Document and @Id
  • Use MongoRepository for basic CRUD
  • Query methods follow the same naming conventions as JPA
  • Use MongoTemplate with Criteria for 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.