MapStruct is a nice tool to generate mappers for converting one Java bean into another e.g. for projections, data-transfer-objects and so on …
As long as fields in source and target beans do match, the mapper is able to generate the data setting automatically .. else we may specify which source fields to map into which target fields or to register custom converters with ease.
Using Maven, we need to add dependencies and plugin integration to our pom.xml
:
<properties>
<org.mapstruct.version>1.5.0.RC1</org.mapstruct.version>
</properties>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Now during the compile phase, the mappers are evaluated and generated, how this works, is shown in the following example.
MapStruct in Action
Let’s say we want to map a User Bean
into a reduced projection … a User DTO
, flattening its properties employer
and country
to a simple string representation.
package com.hascode.example;
public class User {
private String nickname;
private String website;
private Country country;
private Company employer;
// constructor, getter, setter, tostring ...
}
package com.hascode.example;
public class Company {
private String name;
private String address;
// constructor, getter,setter, tostring ...
}
package com.hascode.example;
public class Country {
private String countryCode;
private String name;
// constructor, getter, setter, tostring ....
}
our target projection:
package com.hascode.example;
public class UserDto {
private String nickname;
private String website;
private String country;
private String employer;
// constructor, getter, setter, tostring ...
}
Finally this is our mapper:
package com.hascode.example;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.factory.Mappers;
@Mapper (1)
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); (2)
@BeanMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL) (3)
@Mapping(source = "employer.name", target = "employer") (4)
UserDto mapUser(User user);
default String map(Country country) { (5)
return country.getCountryCode();
}
}
1 | The @Mapper annotation marks this interface as a MapStruct mapper |
2 | Easy client access to our mapper via UserMapper.INSTANCE.mapUser() |
3 | Map every property of the source bean into corresponding properties of the target bean, return null if not assignable |
4 | A custom mapping … map the name property of the user’s employer property to the target DTO’s employer property .. |
5 | A custom conversion from a country object to a string representation of its country code |
Now we want to convert an object using this mapper … there we go …
package com.hascode.example;
public class MappingExample {
public static void main(String[] args) {
var user = new User("timmey123",
"https://www.hascode.com/",
new Country("DE", "Germany"),
new Company("ACME Inc.", "Courtyard 123, Somewhere")
);
var userDto = UserMapper.INSTANCE.mapUser(user);
System.out.printf("user %s converted to dto: %s%n", user, userDto);
}
}
The result is:
user User{nickname='timmey123', website='https://www.hascode.com/', country=Country{countryCode='DE', name='Germany'}, employer=Company{name='ACME Inc.', address='Courtyard 123, Somewhere'}} converted to dto: UserDto{nickname='timmey123', website='https://www.hascode.com/', country='DE', employer='ACME Inc.'}