How to use MapStruct in Spring Boot

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 for mapping  DTO to Entity

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:

  1. Create a Spring Boot Project
  2. Setup in the IDE
  3. Configure H2 Database
  4. Create a JPA Entity 
  5. Create a DTO class
  6. Create a Repository Interface
  7. Create a Mapper Interface
  8. Create a Service Interface
  9. Implement the Service Interface
  10. Create a Controller
  11. 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.

Use MapStruct in Spring Boot for mapping  DTO to Entity

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

Use MapStruct in Spring Boot for mapping  DTO to Entity

GET: http://localhost:8080/api/webseries/list

Use MapStruct in Spring Boot for mapping  DTO to Entity

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.

Leave a Comment