Spring Boot with MongoDB: Spring Data MongoDB in Practice for Production Systems
Learn Spring Data MongoDB with Spring Boot for production-grade applications.
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
- ✓Java 17 or later (we use 21 features like records and sealed classes)
- ✓Spring Boot 3.2+ project with spring-boot-starter-data-mongodb dependency
- ✓MongoDB 7.0+ running locally or in Docker (we use a replica set for transactions)
- ✓Basic understanding of Spring Boot auto-configuration and dependency injection
- ✓Familiarity with MongoDB concepts like collections, documents, and indexes
• Spring Data MongoDB provides a repository abstraction similar to JPA but optimized for document databases • Use MongoTemplate for complex aggregations and custom queries beyond what repositories offer • Always define compound indexes for your most frequent query patterns • Transactions require a replica set and are not available in standalone MongoDB • Avoid embedding large arrays in documents; they can exceed the 16MB document size limit
Think of MongoDB as a giant filing cabinet where each drawer (collection) holds folders (documents) with flexible contents. Spring Data MongoDB is like a personal assistant who knows exactly how to fetch and organize those folders without you having to open every drawer manually. It translates your Java method calls into the right drawer-pulling commands, so you can focus on what's in the folders, not on how to get them.
MongoDB has become the go-to NoSQL database for applications that need flexible schemas, horizontal scaling, and high write throughput. When you pair it with Spring Boot, you get a powerful combination that handles everything from simple CRUD to complex geospatial queries. But here's the thing: many developers treat Mongo like a relational database, and that's where the pain starts. In this tutorial, we'll build a real-world SaaS billing system—think subscription plans, usage tracking, and invoice generation. You'll learn how to model documents for performance, use Spring Data MongoDB's repository and template APIs effectively, and avoid the pitfalls that have caused production outages for teams I've worked with. We'll cover indexing strategies, transactions (yes, they exist in Mongo but with caveats), and how to debug slow queries using MongoDB's explain plan. By the end, you'll have a solid mental model for when to use embedded documents versus references, and how to leverage Spring Data MongoDB's reactive support for high-throughput scenarios. This isn't a hello-world tutorial; it's about building something that won't fall over at 3 AM on a Saturday.
Setting Up Spring Data MongoDB with a Realistic Domain
Let's build a billing system. We have customers, subscription plans, and invoices. The key decision: how to model relationships. In MongoDB, you embed when data is always accessed together and rarely changes independently. For example, subscription plan details are embedded in the customer document because you always show the plan name and price when viewing a customer. But invoices are separate collections because they grow unbounded and you query them independently. Here's how we set up the dependencies in your pom.xml. We use Spring Boot 3.2.1 and the reactive MongoDB driver for better throughput, though we'll stick to imperative code in this tutorial for clarity. The spring-boot-starter-data-mongodb artifact pulls in everything you need: the driver, Spring Data MongoDB, and auto-configuration. Add this to your pom.xml:
What the Official Docs Won't Tell You
The official Spring Data MongoDB documentation is thorough, but it glosses over three realities. First, MongoRepository is great for simple CRUD but becomes a performance trap when you rely on derived query methods with multiple fields. Each derived method generates a query that may or may not use an index. Second, the @Document annotation's collection attribute is case-sensitive on Linux systems; 'Invoice' and 'invoice' are different collections. Third, and most painful, transactions in MongoDB require a replica set. You cannot run transactions on a standalone instance. In development, we use Docker Compose with a three-node replica set. Here's how to configure application.properties for a replica set connection string. The key is the ?replicaSet=rs0 parameter. Without it, transactions will silently fail with a vague 'Transaction numbers are only allowed on a replica set member' error.
Document Modeling: Embedding vs. Referencing
The most critical design decision in MongoDB is how to structure your documents. For our billing system, consider a customer document. We embed the subscription plan details because when you fetch a customer, you always need the plan name and price. But we reference invoices via a list of ObjectId references. Why? Invoices can grow to thousands per customer. Embedding them would exceed the 16MB document limit and force unnecessary data transfer when you only need the customer's name. Here's the Customer document class. Note the use of @DocumentReference for invoices. This tells Spring Data MongoDB to store only the IDs in the customer document, not the full invoice documents. The plan object is embedded directly. This is a hybrid model that balances read performance with document size.
Advanced Querying with MongoTemplate
While MongoRepository handles 80% of queries, you'll need MongoTemplate for the rest. Aggregations, custom update operations, and geospatial queries are where MongoTemplate shines. In our billing system, we need to calculate monthly revenue per plan. That's a pipeline: group invoices by plan name, sum the amounts, and sort. Here's how to do it with MongoTemplate's aggregate method. The Aggregation class provides a fluent API that mirrors MongoDB's aggregation pipeline stages. Notice how we use TypedAggregation to specify the input and output types. This avoids casting and makes the code self-documenting. The result is a list of RevenueByPlan records.
explain() before deploying.Transactions: When and How to Use Them
MongoDB transactions are not ACID in the traditional sense—they are snapshot-isolated and have a 60-second default timeout. Use them sparingly. In our billing system, we need to atomically create an invoice and update the customer's usage counter. If the invoice creation fails, we don't want to increment the counter. Here's how to use Spring's @Transactional annotation with MongoDB. The key requirement: your MongoTemplate bean must be configured with a session. Spring Data MongoDB handles this automatically if you use a replica set connection. The @Transactional annotation wraps the method in a session. Note that you cannot call other transactional methods within the same transaction if they use different MongoTemplate instances. Always use the same MongoTemplate bean.
Indexing Strategies for Query Performance
Without proper indexes, MongoDB becomes a slow, expensive table scan machine. In our billing system, the most common queries are: find invoices by customerId and status, and find customers by email. For the first, a compound index on (customerId, status) is optimal. For the second, a unique index on email is critical for both performance and data integrity. Spring Data MongoDB supports index creation via @Indexed and @CompoundIndex annotations. Here's how to define them on your document classes. The @CompoundIndex annotation accepts an array of field names in order. The order matters: MongoDB can use the index for queries on customerId alone, but not for status alone. Always place the most selective field first. For the email index, we set unique=true to prevent duplicate emails at the database level, which is faster than application-level checks.
explain() to verify index usage before deploying.Error Handling and Debugging in Production
MongoDB errors in Spring Boot are not always straightforward. The most common ones: DuplicateKeyException for unique index violations, DataIntegrityViolationException for schema validation failures, and MongoSocketReadException for network issues. You need a global exception handler that returns meaningful HTTP responses. Here's a @ControllerAdvice that catches MongoDB-specific exceptions and maps them to appropriate HTTP status codes. Note that we use @ExceptionHandler with specific exception types. For DuplicateKeyException, we return 409 Conflict with a message that doesn't expose internal details (like the index name). For MongoSocketReadException, we return 503 Service Unavailable because it's a transient network issue. Never expose the full stack trace to the client; log it server-side and return a sanitized error.
Testing with Testcontainers and Embedded MongoDB
Unit testing MongoDB repositories with mocks is useless—you miss index creation, query generation, and transaction behavior. Use Testcontainers to spin up a real MongoDB instance in your integration tests. For Spring Boot 3.2, the @DataMongoTest annotation slices the context to only MongoDB-related beans. Here's how to write an integration test for our BillingRepository. We use a custom Testcontainers configuration that starts a MongoDB container with a replica set (for transaction testing). The @DynamicPropertySource annotation overrides the MongoDB URI to point to the container. Note that we use a fixed port mapping to simplify configuration, but in CI, use random ports to avoid conflicts.
The Midnight Index Meltdown
- Always analyze slow queries with db.collection.explain('executionStats') before deploying to production
- Default indexes are never enough for real-world query patterns
- Index design should be part of your code review checklist, not an afterthought
explain() on the slow query.db.setProfilingLevel(1, { slowms: 100 })db.system.profile.find({ millis: { $gt: 1000 } }).sort({ ts: -1 }).limit(5).pretty()| File | Command / Code | Purpose |
|---|---|---|
| pom.xml | Setting Up Spring Data MongoDB with a Realistic Domain | |
| application.properties | spring.data.mongodb.uri=mongodb://localhost:27017,localhost:27018,localhost:2701... | What the Official Docs Won't Tell You |
| Customer.java | @Document(collection = "customers") | Document Modeling |
| BillingRepository.java | @Repository | Advanced Querying with MongoTemplate |
| BillingService.java | @Service | Transactions |
| Invoice.java | @Document(collection = "invoices") | Indexing Strategies for Query Performance |
| GlobalExceptionHandler.java | @ControllerAdvice | Error Handling and Debugging in Production |
| BillingRepositoryIntegrationTest.java | @DataMongoTest | Testing with Testcontainers and Embedded MongoDB |
Key takeaways
explain() before deploying to production.Interview Questions on This Topic
Explain how Spring Data MongoDB resolves method names to queries. What are the limitations?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
That's Spring Boot. Mark it forged?
4 min read · try the examples if you haven't