MapStruct is used to map DTO to Entity and vice versa. If we want to use MapStruct in Spring Boot Application, we must add MapStruct dependency in Maven(pom.xm) or Gradle(bulid.gradle) file and create a Mapper interface with @Mapper annotation. This topic will teach us to implement the mapping between DTO and Entity using MapStruct in Spring Boot.
Use MapStruct in Spring Boot
These are the following steps to use MapStruct in Spring to map DTO to Entity and vice versa:
1. Add MapStruct Dependency
First, add MapStruct dependencies in the Maven(pom.xml) or Gradle(build.gradle) of the Spring Application.
Maven Dependency
<!-- MapStruct dependency -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.2</version>
</dependency>
<!-- For MapStruct processor-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.2</version>
<scope>provided</scope>
</dependency>
Gradle Dependency
dependencies {
implementation 'org.mapstruct:mapstruct:1.6.2'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2'
}
We make sure to adjust the version numbers to the latest available versions.
2. Create DTO and Entity Classes
We must create DTO and Entity classes to map DTO to Entity and vice versa using MapStruct.
DTO class
public class WebSeriesDTO {
private Long id;
private String name;
private String releaseDate;
// Getters and Setters
}
Entity class
@Entity
public class WebSeries {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String releaseDate;
// Getters and Setters
}
3. Create the MapStruct Mapper Interface
MapStruct will automatically generate code for mapping between our DTO and Entity. We need to create a mapping interface with @Mapper annotation.
Example WebSeriesMapper Interface:
@Mapper
public interface WebSeriesMapper {
WebSeriesMapper INSTANCE = Mappers.getMapper(WebSeriesMapper.class);
WebSeries dtoToEntity(WebSeriesDTO dto);
WebSeriesDTO entityToDto(WebSeries entity);
}
- This @Mapper annotation marks the interface as a mapping interface and MapStruct generates the implementation for the interface during the build process. The generated implementation contains methods that map the fields between the source and target objects, handling complex conversions that would otherwise require manual coding.
- An instance of the interface implementation can be retrieved from this code:
WebSeriesMapper INSTANCE = Mappers.getMapper(WebSeriesMapper.class);
- We added the above two methods to the interface for mapping DTO to Entity and vice versa.
Step-by-step Guide Use MapStruct in Spring Boot
Let’s create a Spring Boot Application step-by-step guide to implement DTO to Entity Mapping and vice versa using MapStruct. We will create an example of WebSeries saving and retrieving by mapping DTO to Entity and vice versa using API endpoints through the MapStruct library.
These are the following steps:
- Create a Spring Boot Project
- Setup in the IDE
- Configure H2 Database
- Create a JPA Entity
- Create a DTO class
- Create a Repository Interface
- Create a Mapper Interface
- Create a Service Interface
- Implement the Service Interface
- Create a Controller
- Run the Spring Boot Application
1. Create a Spring Boot Project
We are creating a Spring Boot Project from the web tool Spring Initializr. By Providing details for the project and select the following Maven dependencies:
- Spring Web
- Spring Data JPA
- H2 Database
- Lombok
2. Setup in the IDE
We use Eclipse IDE to set up and configure the created Spring Boot Project. You can use other IDE to set up and configure the Spring Boot project.
Project Structure of Spring Boot
This image shows the project structure of Spring Boot in Eclipse IDE.
Maven Dependency
Here is the Maven (pom.xml) for the project which will implement DTO to Entity mapping and vice versa using MapStruct in Spring Boot. In this file, we need to add MapStruct dependencies.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<groupId>com.springjava</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MapStruct dependency -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.2</version>
</dependency>
<!-- For MapStruct processor-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. Configure H2 Database
We are going to configure the H2 database connection in the application.properties file.
application.properties
#H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
4. Create a JPA Entity
Let’s create a JPA Entity class. For example, consider a WebSeries entity and use Lombok for generating setter and getter methods, a constructor, etc.
WebSeries.java
package com.springjava.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Data
@Entity
public class WebSeries {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String releaseDate;
}
5. Create a DTO class
We are creating a DTO class WebSeriesDTO which has same fields.
WebSeriesDTO.java
package com.springjava.dto;
import lombok.Data;
@Data
public class WebSeriesDTO {
private Long id;
private String name;
private String releaseDate;
}
6. Create a Repository Interface
Create a repository interface for the WebSeries JPA Entity class that interface extends the JpaRepository interface to perform persistence operations on the web_series database table.
WebSeriesRepo.java
package com.springjava.dto;
import lombok.Data;
@Data
public class WebSeriesDTO {
private Long id;
private String name;
private String releaseDate;
}
7. Create a Mapper Interface
We are creating a Mapper Interface WebSeriesMapper for mapping DTO to Entity and vice versa using MapStruct.
WebSeriesMapper.java
package com.springjava.utility;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import com.springjava.dto.WebSeriesDTO;
import com.springjava.entity.WebSeries;
@Mapper
public interface WebSeriesMapper {
WebSeriesMapper INSTANCE = Mappers.getMapper(WebSeriesMapper.class);
WebSeries dtoToEntity(WebSeriesDTO dto);
WebSeriesDTO entityToDto(WebSeries entity);
}
8. Create a Service Interface
Create a Service interface WebSeriessService with some method declaration.
WebSeriesService.java
package com.springjava.service;
import java.util.List;
import com.springjava.dto.WebSeriesDTO;
public interface WebSeriesService {
void saveAll(List < WebSeriesDTO > dtoList);
List < WebSeriesDTO > getAll();
}
9. Implement the Service Interface
Implement the WebSeriesService interface in the WebSeriesServiceImpl class. This class is annotated with @Service annotation, where we inject WebSeriesRepo to call all its methods(saveAll() and findAll()).
WebSeriesServiceImpl.java
package com.springjava.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springjava.dto.WebSeriesDTO;
import com.springjava.entity.WebSeries;
import com.springjava.repository.WebSeriesRepo;
import com.springjava.utility.WebSeriesMapper;
@Service
public class WebSeriesServiceImpl implements WebSeriesService {
@Autowired
private WebSeriesRepo webSeriesRepo;
@Override
public void saveAll(List < WebSeriesDTO > dtoList) {
List < WebSeries > webSeriesList = new ArrayList < > ();
for (WebSeriesDTO dto: dtoList) {
//Use MapStruct for converting DTO to Entity with different data type
WebSeries webSeries = WebSeriesMapper.INSTANCE.dtoToEntity(dto);
webSeriesList.add(webSeries);
}
webSeriesRepo.saveAll(webSeriesList);
}
@Override
public List < WebSeriesDTO > getAll() {
List < WebSeriesDTO > dtoList = new ArrayList < > ();
for (WebSeries webSeries: webSeriesRepo.findAll()) {
//Use MapStruct for converting Entity to DTO with different data type
WebSeriesDTO dto = WebSeriesMapper.INSTANCE.entityToDto(webSeries);
dtoList.add(dto);
}
return dtoList;
}
}
- In the above, we have called methods to map DTO to Entity and Entity to DTO.
10. Create a Controller
Create a controller class WebSeriesController. This is annotated with @RestController to make this class a RestController.
WebSeriesController.java
package com.springjava.controller;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springjava.dto.WebSeriesDTO;
import com.springjava.service.WebSeriesService;
@RestController
@RequestMapping("/api/webseries")
public class WebSeriesController {
@Autowired
private WebSeriesService webSeriesService;
@PostMapping("/save-all")
public ResponseEntity < ? > save(@RequestBody List < WebSeriesDTO > dtoList) {
Map < String, Object > respOrder = new LinkedHashMap < String, Object > ();
webSeriesService.saveAll(dtoList);
respOrder.put("status", 1);
respOrder.put("message", "Record is Saved Successfully!");
return new ResponseEntity < > (respOrder, HttpStatus.CREATED);
}
@GetMapping("/list")
public ResponseEntity < ? > getAll() {
Map < String, Object > respOrder = new LinkedHashMap < String, Object > ();
List < WebSeriesDTO > dtoList = webSeriesService.getAll();
if (!dtoList.isEmpty()) {
respOrder.put("status", 1);
respOrder.put("data", dtoList);
return new ResponseEntity < > (respOrder, HttpStatus.OK);
} else {
respOrder.clear();
respOrder.put("status", 0);
respOrder.put("message", "Data is not found");
return new ResponseEntity < > (respOrder, HttpStatus.NOT_FOUND);
}
}
}
11. Run the Spring Boot Application
Right-click this Spring Boot application on the DemoApplication.java, then click Run As and select Java Application.
H2 Database Console
To check the H2 database console use the URL http://localhost:[server_port]/h2-console.
JSON Array
We are creating a sample JSON Array to test the API http://localhost:8080/api/order/save-all.
[
{
"name": "Demo",
"releaseDate": "30-09-2024"
},
{
"name": "Abc",
"releaseDate": "01-10-2024"
}
]
Test the APIs on the Postman Tool
POST: http://localhost:8080/api/webseries/save-all
GET: http://localhost:8080/api/webseries/list
Mapping Fields With Different Field Names
When we map between DTO and Entity using MapStruct, if field names are different in these classes, we need to add a @Mapping annotation in the method of the Mapper interface.
Example
Consider the release field in the Entity class and releaseDate field in the DTO class.
Entity class
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Data
@Entity
public class WebSeries {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String release;
}
DTO class
import lombok.Data;
@Data
public class WebSeriesDTO {
private Long id;
private String name;
private String releaseDate;
}
When mapping different field names of DTO and Entity, we will need to configure each field’s source field to the target field and add an @Mapping annotation for each field.
Mapper Interface
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface WebSeriesMapper {
WebSeriesMapper INSTANCE = Mappers.getMapper(WebSeriesMapper.class);
//Mapping DTO to Entity with different field names
@Mapping(source = "releaseDate", target = "release")
WebSeries dtoToEntity(WebSeriesDTO dto);
//Mapping Entity to DTO with different field names
@Mapping(source = "release", target = "releaseDate")
WebSeriesDTO entityToDto(WebSeries entity);
}
Mapping Fields With Different Field Types
When we map between DTO and Entity using MapStruct, if field types are different in these classes, we need to add @Mapping annotation to the method of the Mapper interface.
Example
Consider the LocalDate type field in the Entity class and String type field in the DTO class.
Entity class
import java.time.LocalDate;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Data
@Entity
public class WebSeries {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDate release;
}
DTO class
import lombok.Data;
@Data
public class WebSeriesDTO {
private Long id;
private String name;
private String releaseDate;
}
When mapping different DTO and Entity field types, we will need to configure each field’s source field to the target field and add an @Mapping annotation for each field type and sometimes we need to write manual conversion codes as per the need.
Mapper Interface
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapp
public interface WebSeriesMapper {
WebSeriesMapper INSTANCE = Mappers.getMapper(WebSeriesMapper.class);
//Mapping DTO to Entity with different field type(String to LocalDate)
@Mapping(source = "releaseDate", target = "release", dateFormat = "d-MM-yyyy")
WebSeries dtoToEntity(WebSeriesDTO dto);
//Mapping Entity to DTO with different field type(LocalDate to String)
@Mapping(source = "release", target = "releaseDate", dateFormat = "d-MM-yyyy")
WebSeriesDTO entityToDto(WebSeries entity);
}
- In the above, we converted String to LocalDate and vice versa.
Conclusion
In this topic, we learned how to use MapStruct in a Spring Boot application for automatic mapping between DTO and Entity, even when the field names and field types differ.