In this article, we will see Guide to MapStruct with Spring Boot, which is a simple mapper for converting one object to another.
The MapStruct API provides the necessary functionalities to convert between two Java Beans.
Why use MapStruct?
We use MapStruct whenever we want to perform some type of conversion between Java objects. For example, if we have a DTO and want to transform it into an entity.
Furthermore, MapStruct simplifies its usage by requiring the creation of an interface and the definition of conversion methods.
How to use MapStruct?
Let’s start by adding its Maven dependency. Keep in mind that we also use Lombok, so we will include it as well:
Maven
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${mapstruct.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </dependency>
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> <configuration> <release>${java.version}</release> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin>
Basic Conversion
Let’s start with the simplest mapper case:
@Getter @Setter public class CarEntity { private String color; private String model; } @Getter @Setter public class CarDto { private String color; private String model; }
We will create the MapStruct interface responsible for mapping:
@Mapper(componentModel = "spring") public interface CarMapper { CarEntity toEntity(CarDto source); CarDto toDto(CarEntity target); }
To use it with Spring IoC, we need to add the componentModel
with the “spring” tag.
The created interface will provide an implementation for the defined methods.
Conversion with Different Fields
Sometimes, the fields between the two objects have different names. In such cases, we can use the @Mapping
property.
@Getter @Setter public class CarEntity { private String colorCode; private String name; } @Getter @Setter public class CarDto { private String color; private String model; }
The new interface uses @Mapping
to map the fields:
@Mapper public interface CarMapper { @Mappings({ @Mapping(target="color", source="colorCode"), @Mapping(target="model", source="name") }) CarDto Dto(CarEntity entity); @Mappings({ @Mapping(target="colorCode", source="color"), @Mapping(target="model", source="name") }) CarEntity toEntity(CarDto dto); }
Conversion with Reference to Other Objects
Sometimes, we need to convert objects with embedded objects. Let’s see an example:
@Getter @Setter public class CarEntity { private String color; private String model; private UserEntity user; } @Getter @Setter public class CarDto { private String color; private String model; private UserDto user; } @Getter @Setter public class UserEntity { private String name; } @Getter @Setter public class UserDto { private String name; }
We will create a new mapper, and the parent mapper will reference it:
@Mapper(componentModel = "spring") public interface UserMapper { UserEntity toEntity(UserDto source); UserDto toDto(UserEntity target); } @Mapper(componentModel = "spring", uses = UserMapper.class)) public interface CarMapper { CarEntity toEntity(CarDto source); CarDto toDto(CarEntity target); }
As we can see in the previous example, we added the uses = UserMapper.class
statement to the parent mapper so that it can map classes of type User
.
Type Conversion with MapStruct
MapStruct offers implicit type conversion, and the most commonly used one is usually the conversion from String
to Date
. Let’s see it with an example:
@Getter @Setter public class CarEntity { private String colorCode; private String name; private String date; } @Getter @Setter public class CarDto { private String color; private String model; private LocalDate date; }
We modify our interface to incorporate this type conversion:
@Mapper(componentModel = "spring") public interface CarMapper { @Mappings({ @Mapping(target="color", source="colorCode"), @Mapping(target="model", source="name"), @Mapping(target="date", source = "date", dateFormat = "dd-MM-yyyy") }) CarDto Dto(CarEntity entity); @Mappings({ @Mapping(target="colorCode", source="color"), @Mapping(target="model", source="name"), @Mapping(target="date", source="date", dateFormat="dd-MM-yyyy") }) CarEntity toEntity(CarDto dto); }
Ignoring a Field with MapStruct
Sometimes, we need to ignore a field, for which we use the ignore
property.
@Getter @Setter public class CarEntity { private String color; private String model; private boolean valueToIgnore; } @Getter @Setter public class CarDto { private String color; private String model; private boolean valueToIgnore; }
@Mapper(componentModel = "spring") public interface CarMapper { @Mapping(ignore = true, target = "valueToIgnore") CarEntity toEntity(CarDto source); @Mapping(ignore = true, target = "valueToIgnore") CarDto toDto(CarEntity target); }
In the previous example, we have achieved that the valueToIgnore
field is ignored both when converting to an entity and when converting to a DTO.
The use of ignore
within MapStruct is essential when working with Hibernate and JPA and using FetchType.LAZY
, as failing to consider lazy loading can result in MapStruct triggering a get
request and fetching all the information. This is one of the common mistakes that is made when working with MapStruct and Hibernate.
BeforeMapping and AfterMapping Annotations in MapStruct
Often, we need to perform some mapping before or after the conversion. For these cases, we use the @BeforeMapping
and @AfterMapping
annotations, such as populating a field based on the object type.
Using @BeforeMapping
in MapStruct allows us to execute the code we create just before the mapping occurs.
Using @AfterMapping
in MapStruct executes after the mapping, allowing us to assign values once the mapping is completed.
@Getter @Setter public class CarEntity { private String colorCode; private String name; } public class PickUpCar extends Car { } public class SedanCar extends Car { } @Getter @Setter public class CarDto { private String color; private String model; private CarType type; } public enum CarType { PICKUP, SEDAN }
@Mapper(componentModel = "spring") public interface CarMapper { CarEntity toEntity(CarDto dto); CarDto Dto(CarEntity entity); @BeforeMapping default void setTypeCar(Car car, @MappingTarget CarDTO carDto) { if (car instanceof SedanCar) { carDto.setType(CarType.SEDAN); } if (car instanceof PickupCar) { carDto.setType(CarType.PICKUP); } } @AfterMapping default void changeColor(@MappingTarget CarDTO carDto) { carDto.setColor("yellow"); } }
Creating a custom mapper with @Named
In some cases, we need to create a mapper to handle specific situations that cannot be directly mapped. For such cases, we can use @Named
. Let’s see an example.
Suppose we have a scenario where we need to convert miles to kilometers, and we have the following objects to convert:
public class CityValuesDTO { private int miles; private String city; //getters setters etc... }
public class CityValues { private double km; private String city; //getters setters etc... }
To perform the mapping from miles to kilometers and vice versa, we will use a mapper and the @Named
annotation.
@Mapper public interface CityValuesValuesMapper { @Named("milesToKm") public static double milesToKilometers(int miles) { return miles * 1.6; } //... }
Now let’s complete the mapper with the necessary mappings:
@Mapper public interface CityValuesMapper { CityValuesValuesMapper INSTANCE = Mappers.getMapper(CityValuesValuesMapper.class); @Mapping(source = "miles", target = "km", qualifiedByName = "milesToKm") public CityValues toDomains(CityValuesDTO dto); @Named("milesToKm") public static double milesToKilometers(int miles) { return miles * 1.6; } }
Conclusion
In this post, we have seen a guide to MapStruct with Spring Boot, exploring some examples. While there are other libraries that can help us perform mapping between our beans, MapStruct may be the one that best fits with Spring, greatly facilitating the work and development time.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!