Spring Boot + MongoDB CRUD Example Tutorial

In this tutorial, we will learn to integrate MongoDB with a Spring Boot Application and perform different CRUD operations (Create, Read, Update, and Delete operations) on the Employee entity.
This tutorial has upgraded to Spring Boot 3 and Java 17.

Application Flow

In this tutorial, we will also learn how to implement a sequential, auto-generated field for MongoDB in Spring Boot. When we're using MongoDB as the database for a Spring Boot application, we can't use @GeneratedValue annotation in our models as it's not available. Hence we need a method to produce the same effect as we'll have if we're using JPA and an SQL database.
MongoDB is a document database with the scalability and flexibility that you want with the querying and indexing that you need. 
Learn Spring boot at https://www.javaguides.net/p/spring-boot-tutorial.html.

Read more about MongoDB at the official website.

Video Tutorial

This tutorial is also explained in below YouTube video:

Setup MongoDB

To work with MongoDB, you need to have MongoDB installed in your system. Check out the official MongoDB doc for installing MongoDB in your System.
 
You can also install the Zip version of MongoDB in Windows, check out this article at https://www.javaguides.net/2019/12/install-mongodb-zip-in-windows.html.

Tools and technologies used

  • Spring Boot - 3
  • Spring Framework - 6
  • MongoDB - 3.8.2
  • JDK - 17 or later
  • Maven - 3.5.1
  • IDE - STS/Eclipse 
Let me list out the development steps so that it will be easy to develop and understand the Spring Boot Application with MongoDB step by step. 

Development Steps

1. Create a Spring Boot Application
2. The pom.xml File - Define Maven Dependencies
3. Project Structure
4. Configuring MongoDB database
5. Creating the Employee Model
6. Create a Spring Data Repository - EmployeeRepository.java
8. Creating the APIs - EmployeeController
9. Exception handling in RESTFul web services
9. Running Spring boot application
10. Test REST APIs Using Postman Client 

1. Create a Spring Boot Application

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.
 
>> Create Spring Boot Project With Spring Initializer
>> Create Spring Boot Project in Spring Tool Suite [STS]

2. Maven Dependencies

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>

3. Project Structure

Following is the packing structure for your reference -

4. Configuring MongoDB database

Spring Boot tries to auto-configure most of the stuff for you based on the dependencies that you have added in the pom.xml file.
Since we have added a spring-boot-starter-mongodb dependency, Spring Boot tries to build a connection with MongoDB by reading the database configuration from the application.properties file.
Let's open an application.properties file and add the following MongoDB properties -
# MONGODB (MongoProperties)
spring.data.mongodb.uri=mongodb://localhost:27017/EmployeeDB
As per our configuration, Mongo DB is running locally at default port 27017.
Note that we need to create an "EmployeeDB" database using the following command:
use EmployeeDB
If the "EmployeeDB" database is not present in MongoDB then it will create a new one.

5. Creating Model Classes

We will create a collection that'll store the auto-incremented sequence for other collections. We'll call this collection database_sequences. It can be created using either the mongo shell or MongoDB Compass. Let's create a corresponding model class:
package net.guides.springboot.crud.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "database_sequences")
public class DatabaseSequence {

    @Id
    private String id;

    private long seq;

    public DatabaseSequence() {}

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public long getSeq() {
        return seq;
    }

    public void setSeq(long seq) {
        this.seq = seq;
    }
}
Let’s now create the Employee model which will be mapped to a Document in the MongoDB database. Create a new package model inside net.guides.springboot.crud(refer project structure section), and add a file Employee.java inside the model package with the following contents -
package net.guides.springboot.crud.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "Employee")
public class Employee {

    @Transient
    public static final String SEQUENCE_NAME = "users_sequence";

    @Id
    private long id;

    @NotBlank
    @Size(max = 100)
    @Indexed(unique = true)
    private String firstName;
    private String lastName;

    @NotBlank
    @Size(max = 100)
    @Indexed(unique = true)
    private String emailId;

    public Employee() {

    }

    public Employee(String firstName, String lastName, String emailId) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.emailId = emailId;
    }

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmailId() {
        return emailId;
    }
    public void setEmailId(String emailId) {
        this.emailId = emailId;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", emailId=" + emailId +
            "]";
    }
}
We have an annotated firstName and emailId with @Indexed annotation and marked it as unique. This creates a unique index on firstName and emailId fields.

6. Create a Spring Data Repository - EmployeeRepository.java

Next, we need to create EmployeeRepository for accessing data from the database.
package net.guides.springboot.crud.repository;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import net.guides.springboot.crud.model.Employee;

@Repository
public interface EmployeeRepository extends MongoRepository<Employee, Long>{

}

7. SequenceGeneratorService - Auto-Generate Sequence

package net.guides.springboot.crud.service;

import static org.springframework.data.mongodb.core.FindAndModifyOptions.options;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;

import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import net.guides.springboot.crud.model.DatabaseSequence;

@Service
public class SequenceGeneratorService {

    private MongoOperations mongoOperations;

    @Autowired
    public SequenceGeneratorService(MongoOperations mongoOperations) {
        this.mongoOperations = mongoOperations;
    }

    public long generateSequence(String seqName) {

        DatabaseSequence counter = mongoOperations.findAndModify(query(where("_id").is(seqName)),
                new Update().inc("seq",1), options().returnNew(true).upsert(true),
                DatabaseSequence.class);
        return !Objects.isNull(counter) ? counter.getSeq() : 1;

    }
}

8. Creating the APIs - EmployeeController

Now, let’s create the APIs which will be exposed to the clients. Notice the usage of annotations @RestController, @RequestMapping, @GetMapping, @PostMapping, and @DeleteMapping to map various URIs to controller methods. Note that we are using the generateSequence() while creating a new record:
package net.guides.springboot.crud.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ja.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import net.guides.springboot.crud.exception.ResourceNotFoundException;
import net.guides.springboot.crud.model.Employee;
import net.guides.springboot.crud.repository.EmployeeRepository;
import net.guides.springboot.crud.service.SequenceGeneratorService;

//@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api/v1")
public class EmployeeController {
    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private SequenceGeneratorService sequenceGeneratorService;

    @GetMapping("/employees")
    public List < Employee > getAllEmployees() {
        return employeeRepository.findAll();
    }

    @GetMapping("/employees/{id}")
    public ResponseEntity < Employee > getEmployeeById(@PathVariable(value = "id") Long employeeId)
    throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
            .orElseThrow(() - > new ResourceNotFoundException("Employee not found for this id :: " + employeeId));
        return ResponseEntity.ok().body(employee);
    }

    @PostMapping("/employees")
    public Employee createEmployee(@Valid @RequestBody Employee employee) {
        employee.setId(sequenceGeneratorService.generateSequence(Employee.SEQUENCE_NAME));
        return employeeRepository.save(employee);
    }

    @PutMapping("/employees/{id}")
    public ResponseEntity < Employee > updateEmployee(@PathVariable(value = "id") Long employeeId,
        @Valid @RequestBody Employee employeeDetails) throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
            .orElseThrow(() - > new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

        employee.setEmailId(employeeDetails.getEmailId());
        employee.setLastName(employeeDetails.getLastName());
        employee.setFirstName(employeeDetails.getFirstName());
        final Employee updatedEmployee = employeeRepository.save(employee);
        return ResponseEntity.ok(updatedEmployee);
    }

    @DeleteMapping("/employees/{id}")
    public Map < String, Boolean > deleteEmployee(@PathVariable(value = "id") Long employeeId)
    throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
            .orElseThrow(() - > new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

        employeeRepository.delete(employee);
        Map < String, Boolean > response = new HashMap < > ();
        response.put("deleted", Boolean.TRUE);
        return response;
    }
}

9. Exception(Error) Handling for RESTful Services

Spring Boot provides a good default implementation for exception handling for RESTful Services. Let’s quickly look at the default Exception Handling features provided by Spring Boot. 

Resource Not Present

Here's what happens when you fire a request to not resource found: http://localhost:8080/some-dummy-url
{
  "timestamp": 1512713804164,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/some-dummy-url"
}
That's a cool error response. It contains all the details that are typically needed.
In the spring boot application, we can specify the Response Status for a specific exception along with the definition of the Exception with the ‘@ResponseStatus’ annotation.

ResourceNotFoundException

Let's create a ResourceNotFoundException class and add the following content to it:
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception{

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message){
        super(message);
    }
}

Customizing Error Response Structure - ErrorDetails.java

The default error response provided by Spring Boot contains all the details that are typically needed. However, you might want to create a framework-independent response structure for your organization. In that case, you can define a specific error response structure. 
Let’s define a simple error response bean:
import java.util.Date;

public class ErrorDetails {
    private Date timestamp;
    private String message;
    private String details;

    public ErrorDetails(Date timestamp, String message, String details) {
        super();
        this.timestamp = timestamp;
        this.message = message;
        this.details = details;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public String getMessage() {
        return message;
    }

    public String getDetails() {
        return details;
    }
}

GlobalExceptionHandler

To use ErrorDetails to return the error response, let’s create a GlobalExceptionHandler class annotated with @ControllerAdvice annotation. This class handles exception-specific and global exceptions in a single place.
import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
    ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> globleExcpetionHandler(Exception ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
The @ControllerAdvice annotation marks the class as a global exception handler for all controllers in the application.

The class has two methods annotated with @ExceptionHandler. These methods handle exceptions of type ResourceNotFoundException and Exception, respectively.

When a ResourceNotFoundException is thrown, the resourceNotFoundException() method is called, which creates an instance of ErrorDetails containing the date, message, and description of the exception, and returns a ResponseEntity with the HttpStatus NOT_FOUND and the ErrorDetails.

When an exception of type Exception is thrown, the globleExcpetionHandler() method is called, which creates an instance of ErrorDetails containing the date, message, and description of the exception, and returns a ResponseEntity with the HttpStatus INTERNAL_SERVER_ERROR and the ErrorDetails.

10. Running Spring boot application

This spring boot application has an entry point Java class called Application.java with the public static void main(String[] args) method, which you can run to start the application.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

11. Test REST APIs Using Postman Client

Create Employee Rest API - Post HTTP Request

Update Employee Rest API - PUT HTTP Request


Get Employee Rest API - Get HTTP Request


Get All Employee Rest API - Get HTTP Request

Delete Employee Rest API - Delete HTTP Request

GitHub Repository

The source code of this tutorial is available on my GitHub repository.

Comments

  1. I am getting the error when i execute this project

    Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.

    Mon Jul 20 23:19:40 IST 2020
    There was an unexpected error (type=Not Found, status=404).

    What is the solution to this error??

    ReplyDelete
    Replies
    1. I am also facing same issue

      Delete
    2. Access the REST endpoints with correct URL. Refer the screenshots images provided in this tutorial to access the REST endpoints.

      Delete
  2. This tutorial is really very good and gives detailed information on each steps. Thanks a lot for this useful topic.

    ReplyDelete
  3. while doing POST request, shouldn't the id be auto generated?

    ReplyDelete

Post a Comment

Leave Comment