AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

Guide to MapStruct with Spring Boot

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!!

Leave a Reply

Your email address will not be published. Required fields are marked *