Spring Boot ModelMapper Tutorial

Introduction

ModelMapper is a Java library that simplifies the process of mapping between different object models, such as Data Transfer Objects (DTOs) and entities. It provides an easy-to-use API for performing object-to-object mapping and can handle complex mappings with nested objects. This tutorial will demonstrate how to use ModelMapper in a Spring Boot application to handle CRUD (Create, Read, Update, Delete) operations using Employee and Department entities.

To learn more about the ModelMapper library, check out this guide: ModelMapper.

How ModelMapper Works

ModelMapper maps objects by matching property names. It can automatically map complex structures, including nested objects. Configuration is minimal, and the library can be used to map any type of object.

Configuration

ModelMapper can be configured as a Spring bean, making it available for dependency injection throughout the application.

Usage

ModelMapper is used to convert between different object models, such as converting an entity to a DTO or vice versa. It significantly reduces the boilerplate code required for manual mapping.

Prerequisites

  1. Java Development Kit (JDK) 17 or later
  2. Apache Maven installed
  3. An IDE like IntelliJ IDEA or Eclipse

Step 1: Create a Spring Boot Project

You can create a Spring Boot project using Spring Initializr or your IDE.

Using Spring Initializr

  1. Go to Spring Initializr.
  2. Select the following options:
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 3.0.0 or later
    • Group: com.example
    • Artifact: modelmapper-demo
    • Name: modelmapper-demo
    • Package name: com.example.modelmapperdemo
    • Packaging: Jar
    • Java: 17 or later
  3. Add the following dependencies:
    • Spring Web
    • Spring Data JPA
    • H2 Database
    • Spring Boot Starter Test
  4. Click "Generate" to download the project zip file.
  5. Extract the zip file and open the project in your IDE.

Step 2: Add ModelMapper Dependency

Add the following dependency to your pom.xml file:

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- H2 Database -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Spring Boot Starter Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- ModelMapper -->
    <dependency>
        <groupId>org.modelmapper</groupId>
        <artifactId>modelmapper</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>

Step 3: Configure Application Properties

Add the following properties to src/main/resources/application.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Step 4: Create Entity and DTO Classes

Create the Department Entity

Create a new Java class named Department in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.List;

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "department")
    private List<Employee> employees;

    // Getters and Setters
    public Department(Long id, String name, List<Employee> employees) {
        this.id = id;
        this.name = name;
        this.employees = employees;
    }

    public Department() {}

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }
}

Create the Employee Entity

Create a new Java class named Employee in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    @ManyToOne
    private Department department;

    // Getters and Setters
    public Employee(Long id, String name, String email, Department department) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.department = department;
    }

    public Employee() {}

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}

Create the Department DTO

Create a new Java class named DepartmentDTO in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import java.util.List;

public class DepartmentDTO {

    private Long id;
    private String name;
    private List<EmployeeDTO> employees;

    // Getters and Setters
    public DepartmentDTO(Long id, String name, List<EmployeeDTO> employees) {
        this.id = id;
        this.name = name;
        this.employees = employees;
    }

    public DepartmentDTO() {}

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<EmployeeDTO> getEmployees() {
        return employees;
    }

    public void setEmployees(List<EmployeeDTO> employees) {
        this.employees = employees;
    }
}

Create the Employee DTO

Create a new Java class named EmployeeDTO in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

public class EmployeeDTO {

    private Long id;
    private String name;
    private String email;
    private DepartmentDTO department;

    // Getters and Setters
    public EmployeeDTO(Long id, String name, String email, DepartmentDTO department) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.department = department;
    }

    public EmployeeDTO() {}

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public DepartmentDTO getDepartment() {
        return department;
    }

    public void setDepartment(DepartmentDTO department) {
        this.department = department;
    }
}

Step 5: Create the Repository Interfaces

DepartmentRepository

Create a new Java interface named DepartmentRepository in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface DepartmentRepository extends JpaRepository<Department, Long> {
}

EmployeeRepository

Create a new Java interface named EmployeeRepository in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Step 6: Create the Service Classes

DepartmentService

Create a new Java class named DepartmentService in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.modelmapper.ModelMapper;
import org.springframework



.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class DepartmentService {

    private final DepartmentRepository departmentRepository;
    private final ModelMapper modelMapper;

    public DepartmentService(DepartmentRepository departmentRepository, ModelMapper modelMapper) {
        this.departmentRepository = departmentRepository;
        this.modelMapper = modelMapper;
    }

    public List<DepartmentDTO> findAll() {
        return departmentRepository.findAll().stream()
                .map(department -> modelMapper.map(department, DepartmentDTO.class))
                .collect(Collectors.toList());
    }

    public Optional<DepartmentDTO> findById(Long id) {
        return departmentRepository.findById(id)
                .map(department -> modelMapper.map(department, DepartmentDTO.class));
    }

    public DepartmentDTO save(DepartmentDTO departmentDTO) {
        Department department = modelMapper.map(departmentDTO, Department.class);
        Department savedDepartment = departmentRepository.save(department);
        return modelMapper.map(savedDepartment, DepartmentDTO.class);
    }

    public void deleteById(Long id) {
        departmentRepository.deleteById(id);
    }
}

EmployeeService

Create a new Java class named EmployeeService in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class EmployeeService {

    private final EmployeeRepository employeeRepository;
    private final ModelMapper modelMapper;

    public EmployeeService(EmployeeRepository employeeRepository, ModelMapper modelMapper) {
        this.employeeRepository = employeeRepository;
        this.modelMapper = modelMapper;
    }

    public List<EmployeeDTO> findAll() {
        return employeeRepository.findAll().stream()
                .map(employee -> modelMapper.map(employee, EmployeeDTO.class))
                .collect(Collectors.toList());
    }

    public Optional<EmployeeDTO> findById(Long id) {
        return employeeRepository.findById(id)
                .map(employee -> modelMapper.map(employee, EmployeeDTO.class));
    }

    public EmployeeDTO save(EmployeeDTO employeeDTO) {
        Employee employee = modelMapper.map(employeeDTO, Employee.class);
        Employee savedEmployee = employeeRepository.save(employee);
        return modelMapper.map(savedEmployee, EmployeeDTO.class);
    }

    public void deleteById(Long id) {
        employeeRepository.deleteById(id);
    }
}

Explanation: The DepartmentService and EmployeeService classes contain methods for CRUD operations. 

They use DepartmentRepository and EmployeeRepository to interact with the database and ModelMapper to map between Department, Employee and their corresponding DTOs.

Step 7: Create the Controller Classes

DepartmentController

Create a new Java class named DepartmentController in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/departments")
public class DepartmentController {

    private final DepartmentService departmentService;

    public DepartmentController(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }

    @GetMapping
    public List<DepartmentDTO> getAllDepartments() {
        return departmentService.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<DepartmentDTO> getDepartmentById(@PathVariable Long id) {
        return departmentService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public DepartmentDTO createDepartment(@RequestBody DepartmentDTO departmentDTO) {
        return departmentService.save(departmentDTO);
    }

    @PutMapping("/{id}")
    public ResponseEntity<DepartmentDTO> updateDepartment(@PathVariable Long id, @RequestBody DepartmentDTO departmentDTO) {
        return departmentService.findById(id)
                .map(existingDepartment -> {
                    departmentDTO.setId(existingDepartment.getId());
                    return ResponseEntity.ok(departmentService.save(departmentDTO));
                })
                .orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteDepartment(@PathVariable Long id) {
        return departmentService.findById(id)
                .map(department -> {
                    departmentService.deleteById(id);
                    return ResponseEntity.noContent().build();
                })
                .orElse(ResponseEntity.notFound().build());
    }
}

EmployeeController

Create a new Java class named EmployeeController in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/employees")
public class EmployeeController {

    private final EmployeeService employeeService;

    public EmployeeController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping
    public List<EmployeeDTO> getAllEmployees() {
        return employeeService.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<EmployeeDTO> getEmployeeById(@PathVariable Long id) {
        return employeeService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public EmployeeDTO createEmployee(@RequestBody EmployeeDTO employeeDTO) {
        return employeeService.save(employeeDTO);
    }

    @PutMapping("/{id}")
    public ResponseEntity<EmployeeDTO> updateEmployee(@PathVariable Long id, @RequestBody EmployeeDTO employeeDTO) {
        return employeeService.findById(id)
                .map(existingEmployee -> {
                    employeeDTO.setId(existingEmployee.getId());
                    return ResponseEntity.ok(employeeService.save(employeeDTO));
                })
                .orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
        return employeeService.findById(id)
                .map(employee -> {
                    employeeService.deleteById(id);
                    return ResponseEntity.noContent().build();
                })
                .orElse(ResponseEntity.notFound().build());
    }
}

Step 8: Configure ModelMapper Bean

Create a new Java configuration class named ModelMapperConfig in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapperConfig {

    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
}

Step 9: Create the Main Application Class

Create a main application class named ModelMapperDemoApplication in the com.example.modelmapperdemo package:

package com.example.modelmapperdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ModelMapperDemoApplication {

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

Explanation: The ModelMapperDemoApplication class contains the main method, which is the entry point of the Spring Boot application. The @SpringBootApplication annotation is a convenience annotation that adds all the following:

  • @Configuration: Tags the class as a source of bean definitions for the application context.
  • @EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.
  • @ComponentScan: Tells Spring to look for other components, configurations, and services in the specified package.

Step 10: Test the Application

Start your Spring Boot application and use tools like Postman or curl to test the CRUD operations for both Department and Employee entities.

Create a Department

  • Method: POST
  • URL: http://localhost:8080/departments
  • Body:
    {
        "name": "IT Department"
    }
    

Get All Departments

  • Method: GET
  • URL: http://localhost:8080/departments

Create an Employee

  • Method: POST
  • URL: http://localhost:8080/employees
  • Body:
    {
        "name": "Amit Sharma",
        "email": "amit.sharma@example.com",
        "department": {
            "id": 1,
            "name": "IT Department"
        }
    }
    

Get All Employees

  • Method: GET
  • URL: http://localhost:8080/employees

Get an Employee by ID

  • Method: GET
  • URL: http://localhost:8080/employees/{id}

Update an Employee

  • Method: PUT
  • URL: http://localhost:8080/employees/{id}
  • Body:
    {
        "name": "Rohit Sharma",
        "email": "rohit.sharma@example.com",
        "department": {
            "id": 1,
            "name": "IT Department"
        }
    }
    

Delete an Employee

  • Method: DELETE
  • URL: http://localhost:8080/employees/{id}

Conclusion

In this tutorial, we demonstrated how to use ModelMapper to handle CRUD operations in a Spring Boot application using Employee and Department entities. We covered the creation of entities, DTOs, repositories, services, controllers, and ModelMapper configuration. By following these steps, you can efficiently use ModelMapper to map

between different object models and simplify your codebase. This approach ensures that your code is type-safe, easy to maintain, and reduces the boilerplate code required for manual mapping.

Comments