Object mapping magic with MapStruct

-

In our projects we often have several Java objects that needs to be mapped to other objects, depending on it’s purpose. One of the most common cases is a domain item that needs a representation on the frontend. Another case is that we want to store data in elasticsearch, but don’t want to use the domain item there as well.

Mapping one object to another is a boring task and could easily lead to mistakes. MapStruct can generate bean mappings at compile-time based on annotations. The generated mapping code uses plain method invocations and thus is fast, type-safe and easy to understand.

How does it work?
Defining a mapping is really simple. First you need to create an interface with the @Mapper annotation. Then add a method that expects the source as parameter and the target as return type.

@Mapper
public interface RelationMapper {
 
    RelationListDTO relationToListDto(Relation relation);
 
}

If your object contains another object you will need to define a mapping for that object as well. So if for example our Relation object contains an Address object and the RelationListDto contains a AddressListDto, the mapper has no idea how to map the properties of Address to our AddressListDto.

public class Relation {
 
    private String name;
    private String email;
    private Address address;
 
    // getters and setters
}
 
public class RelationListDTO {
    private String name;
    private String email;
    private AddressListDto address;
 
    // getters and setters
}

Therefor you need also need to create a mapper for Address and use that mapper in the RelationMapper class.

@Mapper(uses = {AddressMapper.class})
public interface RelationMapper {...}

Instead of defining a separate mapper for Address you can also specify how to map specific properties. Let us change the RelationListDto a little bit so that we only have a few properties instead of an Address object.

public class RelationListDTO {
    private String name;
    private String email;
 
    private String street;
    private String city;
 
    // getters and setters
}

Now we can specify how to map these properties with the @Mapping annotation:

@Mapper
public interface RelationMapper {
 
    @Mapping(source = "address.street", target = "street")
    @Mapping(source = "address.city", target = "city")
    RelationListDTO relationToListDto(Relation relation);
}

Dependency injection
If you use a dependency injection framework you can access the mapper objects via dependency injection as well. Currently they are supporting CDI and Spring Framework.  All you have to do is specify which framework you are using in the @Mapper annotation:

@Mapper(componentModel = "spring")
public interface RelationMapper {} 

Warnings
When you have a mismatch between properties in your objects, MapStruct will generate a warning that will tell you that you have unmapped properties. This can be a completely valid scenario as you maybe not want to map all properties. If this is the case you can add a mapper annotation with the property name and the ignore attribute. This way MapStruct wil skip the property from being mapped.

@Mapping(target = "somePropertyName", ignore = true)

And more..
These examples are just the simple use cases with MapStruct, but there is so much more. Things like implicit type conversions or referencing another mapper in a mapper. You can also customise your mapper with the @BeforeMapping and @AfterMapping which allows you to manipulate the objects before and after the mapper starts.

So it’s not really magic, but I like MapStruct. Just give it a try and maybe it will also help you in your project.