Mapstruct Reference Guide PDF
Mapstruct Reference Guide PDF
2019-09-29
Table of Contents
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2. Set up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2.1. Apache Maven. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2. Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.3. Apache Ant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.4. Configuration options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.5. Using MapStruct on Java 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3. Defining a mapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.1. Basic mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2. Adding custom methods to mappers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.3. Mapping methods with several source parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.4. Updating existing bean instances. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.5. Mappings with direct field access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.6. Using builders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4. Retrieving a mapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.1. The Mappers factory (no dependency injection) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.2. Using dependency injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.3. Injection strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5. Data type conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.1. Implicit type conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.2. Mapping object references . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.3. Controlling nested bean mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.4. Invoking other mappers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.5. Passing the mapping target type to custom mappers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.6. Passing context or state objects to custom methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.7. Mapping method resolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.8. Mapping method selection based on qualifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6. Mapping collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.1. Mapping maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.2. Collection mapping strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6.3. Implementation types used for collection mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
7. Mapping Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
8. Mapping Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
8.1. Mapping enum types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
9. Object factories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
10. Advanced mapping options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
10.1. Default values and constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
10.2. Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
10.3. Default Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
10.4. Determining the result type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
10.5. Controlling mapping result for 'null' arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
10.6. Controlling mapping result for 'null' properties in bean mappings (update mapping methods
52
only).
10.7. Controlling checking result for 'null' properties in bean mapping . . . . . . . . . . . . . . . . . . . . . . . 53
10.8. Source presence checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
10.9. Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
11. Reusing mapping configurations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
11.1. Mapping configuration inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
11.2. Inverse mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
11.3. Shared configurations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
12. Customizing mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
12.1. Mapping customization with decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
12.2. Mapping customization with before-mapping and after-mapping methods . . . . . . . . . . . . . . 63
13. Using the MapStruct SPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
13.1. Custom Accessor Naming Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
13.2. Mapping Exclusion Provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
13.3. Custom Builder Provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Preface
This is the reference documentation of MapStruct, an annotation processor for generating type-
safe, performant and dependency-free bean mapping code. This guide covers all the functionality
provided by MapStruct. In case this guide doesn’t answer all your questions just join the MapStruct
Google group to get help.
You found a typo or other error in this guide? Please let us know by opening an issue in the
MapStruct GitHub repository, or, better yet, help the community and send a pull request for fixing
it!
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International
License.
1. Introduction
MapStruct is a Java annotation processor for the generation of type-safe bean mapping classes.
All you have to do is to define a mapper interface which declares any required mapping methods.
During compilation, MapStruct will generate an implementation of this interface. This
implementation uses plain Java method invocations for mapping between source and target
objects, i.e. no reflection or similar.
Compared to writing mapping code from hand, MapStruct saves time by generating code which is
tedious and error-prone to write. Following a convention over configuration approach, MapStruct
uses sensible defaults but steps out of your way when it comes to configuring or implementing
special behavior.
• Compile-time type safety: Only objects and attributes mapping to each other can be mapped, no
accidental mapping of an order entity into a customer DTO etc.
• mappings are incorrect (cannot find a proper mapping method or type conversion)
2. Set up
MapStruct is a Java annotation processor based on JSR 269 and as such can be used within
command line builds (javac, Ant, Maven etc.) as well as from within your IDE.
1
• org.mapstruct:mapstruct-processor: contains the annotation processor which generates mapper
implementations
...
<properties>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
...
2
If you are working with the Eclipse IDE, make sure to have a current version of
the M2E plug-in. When importing a Maven project configured as shown above, it
will set up the MapStruct annotation processor so it runs right in the IDE,
whenever you save a mapper type. Neat, isn’t it?
Also make sure that your project is using Java 1.8 or later (project properties →
"Java Compiler" → "Compile Compliance Level"). It will not work with older
versions.
2.2. Gradle
Add the following to your Gradle build file in order to enable MapStruct:
...
plugins {
...
id 'net.ltgt.apt' version '0.20'
}
dependencies {
...
implementation "org.mapstruct:mapstruct:$1.3.1.Final"
annotationProcessor "org.mapstruct:mapstruct-processor:$1.3.1.Final"
3
Example 3. Gradle (3.3 and older)
...
plugins {
...
id 'net.ltgt.apt' version '0.20'
}
dependencies {
...
compile "org.mapstruct:mapstruct:$1.3.1.Final"
annotationProcessor "org.mapstruct:mapstruct-processor:$1.3.1.Final"
...
<javac
srcdir="src/main/java"
destdir="target/classes"
classpath="path/to/mapstruct-1.3.1.Final.jar">
<compilerarg line="-processorpath path/to/mapstruct-processor-1.3.1.Final.jar
"/>
<compilerarg line="-s target/generated-sources"/>
</javac>
...
4
2.4. Configuration options
The MapStruct code generator can be configured using annotation processor options.
When invoking javac directly, these options are passed to the compiler in the form -Akey=value.
When using MapStruct via Maven, any processor options can be passed using an options element
within the configuration of the Maven processor plug-in like this:
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>
-Amapstruct.suppressGeneratorTimestamp=true
</compilerArg>
<compilerArg>
-Amapstruct.suppressGeneratorVersionInfoComment=true
</compilerArg>
</compilerArgs>
</configuration>
</plugin>
...
5
Example 6. Gradle configuration
...
compileJava {
options.compilerArgs = [
'-Amapstruct.suppressGeneratorTimestamp=true',
'-Amapstruct.suppressGeneratorVersionInfoComment=true'
]
}
...
6
Option Purpose Default
A core theme of Java 9 is the modularization of the JDK. One effect of this is that a specific module
needs to be enabled for a project in order to use the javax.annotation.Generated annotation.
@Generated is added by MapStruct to generated mapper classes to tag them as generated code,
stating the date of generation, the generator version etc.
To allow usage of the @Generated annotation the module java.xml.ws.annotation must be enabled.
When using Maven, this can be done like this:
If the @Generated annotation is not available, MapStruct will detect this situation and not add it to
generated mappers.
3. Defining a mapper
In this section you’ll learn how to define a bean mapper with MapStruct and which options you
have to do so.
7
Example 7. Java interface to define a mapper
@Mapper
public interface CarMapper {
The @Mapper annotation causes the MapStruct code generator to create an implementation of the
CarMapper interface during build-time.
In the generated method implementations all readable properties from the source type (e.g. Car)
will be copied into the corresponding property in the target type (e.g. CarDto):
• When a property has the same name as its target entity counterpart, it will be mapped
implicitly.
• When a property has a different name in the target entity, its name can be specified via the
@Mapping annotation.
Fluent setters are also supported. Fluent setters are setters that return the same
type as the type being modified.
E.g.
To get a better understanding of what MapStruct does have a look at the following implementation
of the carToCarDto() method as generated by MapStruct:
8
Example 8. Code generated by MapStruct
// GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car) {
if ( car == null ) {
return null;
}
if ( car.getFeatures() != null ) {
carDto.setFeatures( new ArrayList<String>( car.getFeatures() ) );
}
carDto.setManufacturer( car.getMake() );
carDto.setSeatCount( car.getNumberOfSeats() );
carDto.setDriver( personToPersonDto( car.getDriver() ) );
carDto.setPrice( String.valueOf( car.getPrice() ) );
if ( car.getCategory() != null ) {
carDto.setCategory( car.getCategory().toString() );
}
carDto.setEngine( engineToEngineDto( car.getEngine() ) );
return carDto;
}
@Override
public PersonDto personToPersonDto(Person person) {
//...
}
engineDto.setHorsePower(engine.getHorsePower());
engineDto.setFuel(engine.getFuel());
return engineDto;
}
}
The general philosophy of MapStruct is to generate code which looks as much as possible as if you
had written it yourself from hand. In particular this means that the values are copied from source
9
to target by plain getter/setter invocations instead of reflection or similar.
As the example shows the generated code takes into account any name mappings specified via
@Mapping. If the type of a mapped attribute is different in source and target entity, MapStruct will
either apply an automatic conversion (as e.g. for the price property, see also Implicit type
conversions) or optionally invoke / create another mapping method (as e.g. for the driver / engine
property, see also Mapping object references). MapStruct will only create a new mapping method if
and only if the source and target property are properties of a Bean and they themselves are Beans
or simple properties. i.e. they are not Collection or Map type properties.
Collection-typed attributes with the same element type will be copied by creating a new instance of
the target collection type containing the elements from the source property. For collection-typed
attributes with different element types each element will be mapped individually and added to the
target collection (see Mapping collections).
MapStruct takes all public properties of the source and target types into account. This includes
properties declared on super-types.
Alternatively, when using Java 8 or later, you can implement custom methods directly in a mapper
interface as default methods. The generated code will invoke the default methods if the argument
and return types match.
As an example let’s assume the mapping from Person to PersonDto requires some special logic which
can’t be generated by MapStruct. You could then define the mapper from the previous example like
this:
@Mapper
public interface CarMapper {
@Mapping(...)
...
CarDto carToCarDto(Car car);
The class generated by MapStruct implements the method carToCarDto(). The generated code in
10
carToCarDto() will invoke the manually implemented personToPersonDto() method when mapping
the driver attribute.
A mapper could also be defined in the form of an abstract class instead of an interface and
implement the custom methods directly in the mapper class. In this case MapStruct will generate an
extension of the abstract class with implementations of all abstract methods. An advantage of this
approach over declaring default methods is that additional fields could be declared in the mapper
class.
The previous example where the mapping from Person to PersonDto requires some special logic
could then be defined like this:
@Mapper
public abstract class CarMapper {
@Mapping(...)
...
public abstract CarDto carToCarDto(Car car);
@Mapper
public interface AddressMapper {
11
The shown mapping method takes two source parameters and returns a combined target object. As
with single-parameter mapping methods properties are mapped by name.
In case several source objects define a property with the same name, the source parameter from
which to retrieve the property must be specified using the @Mapping annotation as shown for the
description property in the example. An error will be raised when such an ambiguity is not
resolved. For properties which only exist once in the given source objects it is optional to specify
the source parameter’s name as it can be determined automatically.
Specifying the parameter in which the property resides is mandatory when using
the @Mapping annotation.
Mapping methods with several source parameters will return null in case all the
source parameters are null. Otherwise the target object will be instantiated and
all properties from the provided parameters will be propagated.
@Mapper
public interface AddressMapper {
In this case the source parameter is directly mapped into the target as the example above
demonstrates. The parameter hn, a non bean type (in this case java.lang.Integer) is mapped to
houseNumber.
12
Example 13. Update method
@Mapper
public interface CarMapper {
The generated code of the updateCarFromDto() method will update the passed Car instance with the
properties from the given CarDto object. There may be only one parameter marked as mapping
target. Instead of void you may also set the method’s return type to the type of the target parameter,
which will cause the generated implementation to update the passed mapping target and return it
as well. This allows for fluent invocations of mapping methods.
A field is considered as a read accessor if it is public or public final. If a field is static it is not
considered as a read accessor.
A field is considered as a write accessor only if it is public. If a field is final and/or static it is not
considered as a write accessor.
Small example:
13
Example 14. Example classes for mapping
@Mapper
public interface CustomerMapper {
@InheritInverseConfiguration
CustomerDto fromCustomer(Customer customer);
}
For the configuration from above, the generated mapper looks like:
14
Example 15. Generated mapper for example classes
// GENERATED CODE
public class CustomerMapperImpl implements CustomerMapper {
@Override
public Customer toCustomer(CustomerDto customerDto) {
// ...
customer.setId( customerDto.id );
customer.setName( customerDto.customerName );
// ...
}
@Override
public CustomerDto fromCustomer(Customer customer) {
// ...
customerDto.id = customer.getId();
customerDto.customerName = customer.getName();
// ...
}
}
You can find the complete example in the mapstruct-examples-field-mapping project on GitHub.
• The type has a parameterless public static builder creation method that returns a builder. So for
example Person has a public static method that returns PersonBuilder.
• The builder type has a parameterless public method (build method) that returns the type being
build In our example PersonBuilder has a method returning Person.
• In case there are multiple build methods, MapStruct will look for a method called build, if such
method exists then this would be used, otherwise a compilation error would be created.
• A specific build method can be defined by using @Builder within: @BeanMapping, @Mapper or
@MapperConfig
• In case there are multiple builder creation methods that satisfy the above conditions then a
MoreThanOneBuilderCreationMethodException will be thrown from the DefaultBuilderProvider SPI.
In case of a MoreThanOneBuilderCreationMethodException MapStruct will write a warning in the
compilation and not use any builder.
15
If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look
for setters into that type). To finish the mapping MapStruct generates code that will invoke the
build method of the builder.
The Object factories are also considered for the builder type. E.g. If an object
factory exists for our PersonBuilder then this factory would be used instead of the
builder creation method.
16
Example 18. Generated mapper with builder
// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {
builder.name( dto.getName() );
return builder.create();
}
}
• Lombok - requires having the Lombok classes in a separate module. See for more information
rzwitserloot/lombok#1538
• AutoValue
• Immutables - When Immutables are present on the annotation processor path then the
ImmutablesAccessorNamingStrategy and ImmutablesBuilderProvider would be used by default
• FreeBuilder - When FreeBuilder is present on the annotation processor path then the
FreeBuilderAccessorNamingStrategy would be used by default. When using FreeBuilder then the
JavaBean convention should be followed, otherwise MapStruct won’t recognize the fluent
getters.
• It also works for custom builders (handwritten ones) if the implementation supports the
defined rules for the default BuilderProvider. Otherwise, you would need to write a custom
BuilderProvider
In case you want to disable using builders then you can use the
NoOpBuilderProvider by creating a org.mapstruct.ap.spi.BuilderProvider file in the
META-INF/services directory with org.mapstruct.ap.spi.NoOpBuilderProvider as it’s
content.
4. Retrieving a mapper
4.1. The Mappers factory (no dependency injection)
When not using a DI framework, Mapper instances can be retrieved via the
org.mapstruct.factory.Mappers class. Just invoke the getMapper() method, passing the interface type
17
of the mapper to return:
By convention, a mapper interface should define a member called INSTANCE which holds a single
instance of the mapper type:
@Mapper
public interface CarMapper {
@Mapper
public abstract class CarMapper {
This pattern makes it very easy for clients to use mapper objects without repeatedly instantiating
new instances:
Note that mappers generated by MapStruct are stateless and thread-safe and thus can safely be
accessed from several threads at the same time.
18
4.2. Using dependency injection
If you’re working with a dependency injection framework such as CDI (Contexts and Dependency
TM
Injection for Java EE) or the Spring Framework, it is recommended to obtain mapper objects via
dependency injection and not via the Mappers class as described above. For that purpose you can
specify the component model which generated mapper classes should be based on either via
@Mapper#componentModel or using a processor option as described in Configuration options.
Currently there is support for CDI and Spring (the latter either via its custom annotations or using
the JSR 330 annotations). See Configuration options for the allowed values of the componentModel
attribute which are the same as for the mapstruct.defaultComponentModel processor option. In both
cases the required annotations will be added to the generated mapper implementations classes in
order to make the same subject to dependency injection. The following shows an example using
CDI:
@Mapper(componentModel = "cdi")
public interface CarMapper {
The generated mapper implementation will be marked with the @ApplicationScoped annotation and
thus can be injected into fields, constructor arguments etc. using the @Inject annotation:
@Inject
private CarMapper mapper;
A mapper which uses other mapper classes (see Invoking other mappers) will obtain these mappers
using the configured component model. So if CarMapper from the previous example was using
another mapper, this other mapper would have to be an injectable CDI bean as well.
19
Example 25. Using constructor injection
The generated mapper will inject all classes defined in the uses attribute. When
InjectionStrategy#CONSTRUCTOR is used, the constructor will have the appropriate annotation and
the fields won’t. When InjectionStrategy#FIELD is used, the annotation is on the field itself. For now,
the default injection strategy is field injection. It is recommended to use constructor injection to
simplify testing.
Another example are references to other objects which should be mapped to the corresponding
types in the target model. E.g. the class Car might have a property driver of the type Person which
needs to be converted into a PersonDto object when mapping a Car object.
In this section you’ll learn how MapStruct deals with such data type conversions.
• Between all Java primitive data types and their corresponding wrapper types, e.g. between int
and Integer, boolean and Boolean etc. The generated code is null aware, i.e. when converting a
wrapper type into the corresponding primitive type a null check will be performed.
• Between all Java primitive number types and the wrapper types, e.g. between int and long or
byte and Integer.
20
Converting from larger data types to smaller ones (e.g. from long to int) can cause
a value or precision loss. The Mapper and MapperConfig annotations have a method
typeConversionPolicy to control warnings / errors. Due to backward compatibility
reasons the default value is 'ReportingPolicy.IGNORE`.
• Between all Java primitive types (including their wrappers) and String, e.g. between int and
String or Boolean and String. A format string as understood by java.text.DecimalFormat can be
specified.
@Mapper
public interface CarMapper {
@IterableMapping(numberFormat = "$#.00")
List<String> prices(List<Integer> prices);
}
@Mapper
public interface CarMapper {
21
Example 28. Conversion from Date to String
@Mapper
public interface CarMapper {
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> dates);
}
• When converting from a String, omitting Mapping#dateFormat, it leads to usage of the default
pattern and date format symbols for the default locale. An exception to this rule is
XmlGregorianCalendar which results in parsing the String according to XML Schema 1.0 Part 2,
Section 3.2.7-14.1, Lexical Representation.
22
• When converting from a String, the value needs to be a valid ISO-4217 alphabetic code
otherwise an IllegalArgumentException is thrown
In this case just define a mapping method for the referenced object type as well:
@Mapper
public interface CarMapper {
The generated code for the carToCarDto() method will invoke the personToPersonDto() method for
mapping the driver attribute, while the generated implementation for personToPersonDto()
performs the mapping of person objects.
That way it is possible to map arbitrary deep object graphs. When mapping from entities into data
transfer objects it is often useful to cut references to other entities at a certain point. To do so,
implement a custom mapping method (see the next section) which e.g. maps a referenced entity to
its id in the target object.
When generating the implementation of a mapping method, MapStruct will apply the following
routine for each attribute pair in the source and target object:
• If source and target attribute have the same type, the value will be simply copied from source to
target. If the attribute is a collection (e.g. a List) a copy of the collection will be set into the
target attribute.
• If source and target attribute type differ, check whether there is another mapping method
which has the type of the source attribute as parameter type and the type of the target attribute
as return type. If such a method exists it will be invoked in the generated mapping
implementation.
• If no such method exists MapStruct will look whether a built-in conversion for the source and
target type of the attribute exists. If this is the case, the generated mapping code will apply this
conversion.
• If no such method was found MapStruct will try to generate an automatic sub-mapping method
that will do the mapping between the source and target attributes.
23
• If MapStruct could not create a name based mapping method an error will be raised at build
time, indicating the non-mappable attribute and its path.
The ‘.’ notation in an @Mapping source or target type can be used to control how properties should be
mapped when names do not match. There is an elaborate example in our examples repository to
explain how this problem can be overcome.
In the simplest scenario there’s a property on a nested level that needs to be corrected. Take for
instance a property fish which has an identical name in FishTankDto and FishTank. For this property
MapStruct automatically generates a mapping: FishDto fishToFishDto(Fish fish). MapStruct cannot
possibly be aware of the deviating properties kind and type. Therefore this can be addressed in a
mapping rule: @Mapping(target="fish.kind", source="fish.type"). This tells MapStruct to deviate
from looking for a name kind at this level and map it to type.
@Mapper
public interface FishTankMapper {
The same constructs can be used to ignore certain properties at a nesting level, as is demonstrated
in the second @Mapping rule.
MapStruct can even be used to “cherry pick” properties when source and target do not share the
same nesting level (the same number of properties). This can be done in the source – and in the
target type. This is demonstrated in the next 2 rules: @Mapping(target="ornament",
24
source="interior.ornament") and @Mapping(target="material.materialType", source="material").
The latter can even be done when mappings first share a common base. For example: all properties
that share the same name of Quality are mapped to QualityDto. Likewise, all properties of Report
are mapped to ReportDto, with one exception: organisation in OrganisationDto is left empty (since
there is no organization at the source level). Only the name is populated with the organisationName
from Report. This is demonstrated in @Mapping(target="quality.report.organisation.name",
source="quality.report.organisationName")
Coming back to the original example: what if kind and type would be beans themselves? In that case
MapStruct would again generate a method continuing to map. Such is demonstrated in the next
example:
@Mapper
public interface FishTankMapperWithDocument {
MapStruct will perform a null check on each nested property in the source.
In some cases the ReportingPolicy that is going to be used for the generated
nested method would be IGNORE. This means that it is possible for MapStruct not
to report unmapped target properties in nested mappings.
25
5.4. Invoking other mappers
In addition to methods defined on the same mapper type MapStruct can also invoke mapping
methods defined in other classes, be it mappers generated by MapStruct or hand-written mapping
methods. This can be useful to structure your mapping code in several classes (e.g. with one
mapper type per application module) or if you want to provide custom mapping logic which can’t
be generated by MapStruct.
For instance the Car class might contain an attribute manufacturingDate while the corresponding
DTO attribute is of type String. In order to map this attribute, you could implement a mapper class
like this:
In the @Mapper annotation at the CarMapper interface reference the DateMapper class like this:
@Mapper(uses=DateMapper.class)
public class CarMapper {
When generating code for the implementation of the carToCarDto() method, MapStruct will look for
a method which maps a Date object into a String, find it on the DateMapper class and generate an
invocation of asString() for mapping the manufacturingDate attribute.
26
Generated mappers retrieve referenced mappers using the component model configured for them.
If e.g. CDI was used as component model for CarMapper, DateMapper would have to be a CDI bean as
well. When using the default component model, any hand-written mapper classes to be referenced
by MapStruct generated mappers must declare a public no-args constructor in order to be
instantiable.
For instance, the CarDto could have a property owner of type Reference that contains the primary key
of a Person entity. You could now create a generic custom mapper that resolves any Reference
objects to their corresponding managed JPA entity instances.
@PersistenceContext
private EntityManager entityManager;
27
Example 35. Generated code
//GENERATED CODE
@ApplicationScoped
public class CarMapperImpl implements CarMapper {
@Inject
private ReferenceMapper referenceMapper;
@Override
public Car carDtoToCar(CarDto carDto) {
if ( carDto == null ) {
return null;
}
return car;
}
}
@Context parameters are searched for @ObjectFactory methods, which are called on the provided
context parameter value if applicable.
@Context parameters are also searched for @BeforeMapping / @AfterMapping methods, which are called
on the provided context parameter value if applicable.
Note: no null checks are performed before calling before/after mapping methods on context
parameters. The caller needs to make sure that null is not passed in that case.
For generated code to call a method that is declared with @Context parameters, the declaration of
the mapping method being generated needs to contain at least those (or assignable) @Context
parameters as well. The generated code will not create new instances of missing @Context
parameters nor will it pass a literal null instead.
28
Example 36. Using @Context parameters for passing data down to hand-written property mapping
methods
//GENERATED CODE
public CarDto toCar(Car car, Locale translationLocale) {
if ( car == null ) {
return null;
}
return carDto;
}
The algorithm for finding a mapping or factory method resembles Java’s method resolution
algorithm as much as possible. In particular, methods with a more specific source type will take
precedence (e.g. if there are two methods, one which maps the searched source type, and another
one which maps a super-type of the same). In case more than one most-specific method is found, an
error will be raised.
29
When working with JAXB, e.g. when converting a String to a corresponding
JAXBElement<String>, MapStruct will take the scope and name attributes of
@XmlElementDecl annotations into account when looking for a mapping method.
This makes sure that the created JAXBElement instances will have the right QNAME
value. You can find a test which maps JAXB objects here.
So, let’s say there is a hand-written method to map titles with a String return type and String
argument amongst many other referenced mappers with the same String return type - String
argument signature:
Example 38. Several mapping methods with identical source and target types
And a mapper using this handwritten mapper, in which source and target have a property 'title'
that should be mapped:
Without the use of qualifiers, this would result in an ambiguous mapping method error, because 2
30
qualifying methods are found (translateTitleEG, translateTitleGE) and MapStruct would not have a
hint which one to choose.
import org.mapstruct.Qualifier;
@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TitleTranslator {
}
And, some qualifiers to indicate which translator to use to map from source language to target
language:
import org.mapstruct.Qualifier;
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EnglishToGerman {
}
import org.mapstruct.Qualifier;
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface GermanToEnglish {
}
Please take note of the target TitleTranslator on type level, EnglishToGerman, GermanToEnglish on
method level!
Then, using the qualifiers, the mapping could look like this:
31
Example 42. Mapper using qualifiers
@TitleTranslator
public class Titles {
@EnglishToGerman
public String translateTitleEG(String title) {
// some mapping logic
}
@GermanToEnglish
public String translateTitleGE(String title) {
// some mapping logic
}
}
Please make sure the used retention policy equals retention policy CLASS
(@Retention(RetentionPolicy.CLASS)).
A class / method annotated with a qualifier will not qualify anymore for
mappings that do not have the qualifiedBy element.
In many occasions, declaring a new annotation to aid the selection process can be too much for
what you try to achieve. For those situations, MapStruct has the @Named annotation. This annotation
is a pre-defined qualifier (annotated with @Qualifier itself) and can be used to name a Mapper or,
more directly a mapping method by means of its value. The same example above would look like:
32
Example 44. Custom mapper, annotating the methods to qualify by means of @Named
@Named("TitleTranslator")
public class Titles {
@Named("EnglishToGerman")
public String translateTitleEG(String title) {
// some mapping logic
}
@Named("GermanToEnglish")
public String translateTitleGE(String title) {
// some mapping logic
}
}
Although the used mechanism is the same, the user has to be a bit more careful.
Refactoring the name of a defined qualifier in an IDE will neatly refactor all other
occurrences as well. This is obviously not the case for changing a name.
6. Mapping collections
The mapping of collection types (List, Set etc.) is done in the same way as mapping bean types, i.e.
by defining mapping methods with the required source and target types in a mapper interface.
MapStruct supports a wide range of iterable types from the Java Collection Framework.
The generated code will contain a loop which iterates over the source collection, converts each
element and puts it into the target collection. If a mapping method for the collection element types
is found in the given mapper or the mapper it uses, this method is invoked to perform the element
conversion. Alternatively, if an implicit conversion for the source and target element types exists,
this conversion routine will be invoked. The following shows an example:
33
Example 46. Mapper with collection mapping methods
@Mapper
public interface CarMapper {
The generated implementation of the integerSetToStringSet performs the conversion from Integer
to String for each element, while the generated carsToCarDtos() method invokes the carToCarDto()
method for each contained element as shown in the following:
34
Example 47. Generated collection mapping methods
//GENERATED CODE
@Override
public Set<String> integerSetToStringSet(Set<Integer> integers) {
if ( integers == null ) {
return null;
}
return set;
}
@Override
public List<CarDto> carsToCarDtos(List<Car> cars) {
if ( cars == null ) {
return null;
}
return list;
}
Note that MapStruct will look for a collection mapping method with matching parameter and
return type, when mapping a collection-typed attribute of a bean, e.g. from Car#passengers (of type
List<Person>) to CarDto#passengers (of type List<PersonDto>).
//GENERATED CODE
carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) );
...
Some frameworks and libraries only expose JavaBeans getters but no setters for collection-typed
properties. Types generated from an XML schema using JAXB adhere to this pattern by default. In
this case the generated code for mapping such a property invokes its getter and adds all the
35
mapped elements:
//GENERATED CODE
carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) );
...
It is not allowed to declare mapping methods with an iterable source and a non-
iterable target or the other way around. An error will be raised when detecting
this situation.
@MapMapping(valueDateFormat = "dd.MM.yyyy")
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
Similar to iterable mappings, the generated code will iterate through the source map, convert each
value and key (either by means of an implicit conversion or by invoking another mapping method)
and put them into the target map:
36
Example 51. Generated implementation of map mapping method
//GENERATED CODE
@Override
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
if ( source == null ) {
return null;
}
return map;
}
In the table below, the dash - indicates a property name. Next, the trailing s indicates the plural
form. The table explains the options and how they are applied to the presence/absense of a set-s,
add- and / or get-s method on the target object:
Option Only target Only target Both set-s / No set-s / add- Existing
set-s Available add- Available add- Available Available Target(@Target
Type)
37
Option Only target Only target Both set-s / No set-s / add- Existing
set-s Available add- Available add- Available Available Target(@Target
Type)
Some background: An adder method is typically used in case of generated (JPA) entities, to add a
single element (entity) to an underlying collection. Invoking the adder establishes a parent-child
relation between parent - the bean (entity) on which the adder is invoked - and its child(ren), the
elements (entities) in the collection. To find the appropriate adder, MapStruct will try to make a
match between the generic parameter type of the underlying collection and the single argument of
a candidate adder. When there are more candidates, the plural setter / getter name is converted to
singular and will be used in addition to make a match.
The option DEFAULT should not be used explicitly. It is used to distinguish between an explicit user
desire to override the default in a @MapperConfig from the implicit Mapstruct choice in a @Mapper.
The option DEFAULT is synonymous to ACCESSOR_ONLY.
When working with an adder method and JPA entities, Mapstruct assumes that
the target collections are initialized with a collection implementation (e.g. an
ArrayList). You can use factories to create a new target entity with intialized
collections instead of Mapstruct creating the target entity by its constructor.
Iterable ArrayList
Collection ArrayList
List ArrayList
Set HashSet
SortedSet TreeSet
NavigableSet TreeSet
Map HashMap
SortedMap TreeMap
38
Interface type Implementation type
NavigableMap TreeMap
ConcurrentMap ConcurrentHashMap
ConcurrentNavigableMap ConcurrentSkipListMap
7. Mapping Streams
The mapping of java.util.Stream is done in a similar way as the mapping of collection types, i.e. by
defining mapping methods with the required source and target types in a mapper interface.
The generated code will contain the creation of a Stream from the provided Iterable/array or will
collect the provided Stream into an Iterable/array. If a mapping method or an implicit conversion
for the source and target element types exists, then this conversion will be done in Stream#map().
The following shows an example:
@Mapper
public interface CarMapper {
39
Example 53. Generated stream mapping methods
//GENERATED CODE
@Override
public Set<String> integerStreamToStringSet(Stream<Integer> integers) {
if ( integers == null ) {
return null;
}
@Override
public List<CarDto> carsToCarDtos(Stream<Car> cars) {
if ( cars == null ) {
return null;
}
The same implementation types as in Implementation types used for collection mappings are used
for the creation of the collection when doing Stream to Iterable mapping.
8. Mapping Values
8.1. Mapping enum types
MapStruct supports the generation of methods which map one Java enum type into another.
By default, each constant from the source enum is mapped to a constant with the same name in the
target enum type. If required, a constant from the source enum may be mapped to a constant with
another name with help of the @ValueMapping annotation. Several constants from the source enum
can be mapped to the same constant in the target type.
40
Example 54. Enum mapping method
@Mapper
public interface OrderMapper {
@ValueMappings({
@ValueMapping(source = "EXTRA", target = "SPECIAL"),
@ValueMapping(source = "STANDARD", target = "DEFAULT"),
@ValueMapping(source = "NORMAL", target = "DEFAULT")
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
// GENERATED CODE
public class OrderMapperImpl implements OrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return null;
}
ExternalOrderType externalOrderType_;
switch ( orderType ) {
case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL;
break;
case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT;
break;
case NORMAL: externalOrderType_ = ExternalOrderType.DEFAULT;
break;
case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
break;
case B2B: externalOrderType_ = ExternalOrderType.B2B;
break;
default: throw new IllegalArgumentException( "Unexpected enum
constant: " + orderType );
}
return externalOrderType_;
}
}
41
By default an error will be raised by MapStruct in case a constant of the source enum type does not
have a corresponding constant with the same name in the target type and also is not mapped to
another constant via @ValueMapping. This ensures that all constants are mapped in a safe and
predictable manner. The generated mapping method will throw an IllegalStateException if for
some reason an unrecognized source value occurs.
MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default.
This can be used only once in a set of value mappings. It comes in two flavors: <ANY_REMAINING> and
<ANY_UNMAPPED>.
In case of source <ANY_REMAINING> MapStruct will continue to map a source enum constant to a
target enum constant with the same name. The remainder of the source enum constants will be
mapped to the target specified in the @ValueMapping with <ANY_REMAINING> source.
MapStruct will not attempt such name based mapping for <ANY_UNMAPPED> and directly apply the
target specified in the @ValueMapping with <ANY_UNMAPPED> source to the remainder.
MapStruct is able to handle null sources and null targets by means of the <NULL> keyword.
@Mapper
public interface SpecialOrderMapper {
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
@ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL"
)
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
42
Example 57. Enum mapping method result, <NULL> and <ANY_REMAINING>
// GENERATED CODE
public class SpecialOrderMapperImpl implements SpecialOrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return ExternalOrderType.DEFAULT;
}
ExternalOrderType externalOrderType_;
switch ( orderType ) {
case STANDARD: externalOrderType_ = null;
break;
case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
break;
case B2B: externalOrderType_ = ExternalOrderType.B2B;
break;
default: externalOrderType_ = ExternalOrderType.SPECIAL;
}
return externalOrderType_;
}
}
Note: MapStruct would have refrained from mapping the RETAIL and B2B when <ANY_UNMAPPED> was
used instead of <ANY_REMAINING>.
9. Object factories
By default, the generated code for mapping one bean type into another or updating a bean will call
the default constructor to instantiate the target type.
Alternatively you can plug in custom object factories which will be invoked to obtain instances of
the target type. One use case for this is JAXB which creates ObjectFactory classes for obtaining new
instances of schema types.
To make use of custom factories register them via @Mapper#uses() as described in Invoking other
mappers, or implement them directly in your mapper. When creating the target object of a bean
mapping, MapStruct will look for a parameterless method, a method annotated with
@ObjectFactory, or a method with only one @TargetType parameter that returns the required target
43
type and invoke this method instead of calling the default constructor:
44
//GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car) {
if ( car == null ) {
return null;
}
//map properties...
return carDto;
}
@Override
public Car carDtoToCar(CarDto carDto) {
if ( carDto == null ) {
return null;
}
//map properties...
return car;
}
}
45
//GENERATED CODE
public class OwnerMapperImpl implements OwnerMapper {
@Override
public void updateOwnerDto(Owner owner, @MappingTarget OwnerDto ownerDto) {
if ( owner == null ) {
return;
}
if ( owner.getCar() != null ) {
if ( ownerDto.getCar() == null ) {
ownerDto.setCar( dtoFactory.createCarDto() );
}
// update car within ownerDto
}
else {
ownerDto.setCar( null );
}
@Override
public void updateOwner(OwnerDto ownerDto, @MappingTarget Owner owner) {
if ( ownerDto == null ) {
return;
}
if ( ownerDto.getCar() != null ) {
if ( owner.getCar() == null ) {
owner.setCar( entityFactory.createEntity( Car.class ) );
}
// update car within owner
}
else {
owner.setCar( null );
}
46
In addition, annotating a factory method with @ObjectFactory lets you gain access to the mapping
sources. Source objects can be added as parameters in the same way as for mapping method. The
@ObjectFactory annotation is necessary to let MapStruct know that the given method is only a
factory method.
@ObjectFactory
public CarDto createCarDto(Car car) {
return // ... custom factory logic
}
}
A mapping with a constant must not include a reference to a source property. The following
example shows some mappings using default values and constants:
47
Example 61. Mapping method with default values and constants
@Mapper(uses = StringListMapper.class)
public interface SourceTargetMapper {
If s.getStringProp() == null, then the target property stringProperty will be set to "undefined"
instead of applying the value from s.getStringProp(). If s.getLongProperty() == null, then the
target property longProperty will be set to -1. The String "Constant Value" is set as is to the target
property stringConstant. The value "3001" is type-converted to the Long (wrapper) class of target
property longWrapperConstant. Date properties also require a date format. The constant "jack-jill-
tom" demonstrates how the hand-written class StringListMapper is invoked to map the dash-
separated list into a List<String>.
10.2. Expressions
By means of Expressions it will be possible to include constructs from a number of languages.
Currently only Java is supported as a language. This feature is e.g. useful to invoke constructors.
The entire source object is available for usage in the expression. Care should be taken to insert only
valid Java code: MapStruct will not validate the expression at generation-time, but errors will show
up in the generated classes during compilation.
The example below demonstrates how two source properties can be mapped to one target:
48
Example 62. Mapping method using an expression
@Mapper
public interface SourceTargetMapper {
@Mapping(target = "timeAndFormat",
expression = "java( new org.sample.TimeAndFormat( s.getTime(),
s.getFormat() ) )")
Target sourceToTarget(Source s);
}
The example demonstrates how the source properties time and format are composed into one target
property TimeAndFormat. Please note that the fully qualified package name is specified because
MapStruct does not take care of the import of the TimeAndFormat class (unless it’s used otherwise
explicitly in the SourceTargetMapper). This can be resolved by defining imports on the @Mapper
annotation.
imports org.sample.TimeAndFormat;
@Mapping(target = "timeAndFormat",
expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")
Target sourceToTarget(Source s);
}
The same warnings and restrictions apply to default expressions that apply to expressions. Only
Java is supported, and MapStruct will not validate the expression at generation-time.
The example below demonstrates how two source properties can be mapped to one target:
49
Example 64. Mapping method using a default expression
imports java.util.UUID;
The example demonstrates how to use defaultExpression to set an ID field if the source field is null,
this could be used to take the existing sourceId from the source object if it is set, or create a new Id
if it isn’t. Please note that the fully qualified package name is specified because MapStruct does not
take care of the import of the UUID class (unless it’s used otherwise explicitly in the
SourceTargetMapper). This can be resolved by defining imports on the @Mapper annotation ((see
Expressions).
50
Example 65. Specifying the result type of a bean mapping method
So, which Fruit must be factorized in the mapping method Fruit map(FruitDto source);? A Banana
or an Apple? Here’s were the @BeanMapping#resultType comes in handy. It controls the factory
method to select, or in absence of a factory method, the return type to create.
• Bean mappings: an 'empty' target bean will be returned, with the exception of constants and
51
expressions, they will be populated when present.
However:
1. By specifying nullValuePropertyMappingStrategy =
NullValuePropertyMappingStrategy.SET_TO_DEFAULT on @Mapping, @BeanMapping, @Mapper or
@MappingConfig, the mapping result can be altered to return default values. For List MapStruct
generates an ArrayList, for Map a HashMap, for arrays an empty array, for String "" and for
primitive / boxed types a representation of false or 0. For all other objects an new instance is
created. Please note that a default constructor is required. If not available, use the
@Mapping#defaultValue.
52
10.7. Controlling checking result for 'null' properties
in bean mapping
MapStruct offers control over when to generate a null check. By default (nullValueCheckStrategy =
NullValueCheckStrategy.ON_IMPLICIT_CONVERSION) a null check will be generated for:
• direct setting of source value to target value when target is primitive and source is not.
b. calling another type conversion and subsequently calling the setter on the target.
c. calling a mapping method and subsequently calling the setter on the target.
First calling a mapping method on the source property is not protected by a null check. Therefor
generated mapping methods will do a null check prior to carrying out mapping on a source
property. Handwritten mapping methods must take care of null value checking. They have the
possibility to add 'meaning' to null. For instance: mapping null to a default value.
The source presence checker name can be changed in the MapStruct service
provider interface (SPI). It can also be deactivated in this way.
10.9. Exceptions
Calling applications may require handling of exceptions when calling a mapping method. These
exceptions could be thrown by hand-written logic and by the generated built-in mapping methods
or type-conversions of MapStruct. When the calling application requires handling of exceptions, a
53
throws clause can be defined in the mapping method:
@Mapper(uses = HandWritten.class)
public interface CarMapper {
MapStruct now, wraps the FatalException in a try-catch block and rethrows an unchecked
RuntimeException. MapStruct delegates handling of the GearException to the application logic
because it is defined as throws clause in the carToCarDto method:
54
Example 68. try-catch block in generated implementation
// GENERATED CODE
@Override
public CarDto carToCarDto(Car car) throws GearException {
if ( car == null ) {
return null;
}
return carDto;
}
Some notes on null checks. MapStruct does provide null checking only when required: when
applying type-conversions or constructing a new type by invoking its constructor. This means that
the user is responsible in hand-written code for returning valid non-null objects. Also null objects
can be handed to hand-written code, since MapStruct does not want to make assumptions on the
meaning assigned by the user to a null object. Hand-written code has to deal with this.
55
Example 69. Update method inheriting its configuration
@Mapper
public interface CarMapper {
@InheritConfiguration
void carDtoIntoCar(CarDto carDto, @MappingTarget Car car);
}
The example above declares a mapping method carDtoToCar() with a configuration to define how
the property numberOfSeats in the type Car shall be mapped. The update method that performs the
mapping on an existing instance of Car needs the same configuration to successfully map all
properties. Declaring @InheritConfiguration on the method lets MapStruct search for inheritance
candidates to apply the annotations of the method that is inherited from.
One method A can inherit the configuration from another method B if all types of A (source types
and result type) are assignable to the corresponding types of B.
Methods that are considered for inheritance need to be defined in the current mapper, a super
class/interface, or in the shared configuration interface (as described in Shared configurations).
In case more than one method is applicable as source for the inheritance, the method name must
be specified within the annotation: @InheritConfiguration( name = "carDtoToCar" ).
A method can use @InheritConfiguration and override or amend the configuration by additionally
applying @Mapping, @BeanMapping, etc.
Use the annotation @InheritInverseConfiguration to indicate that a method shall inherit the inverse
configuration of the corresponding reverse method.
56
Example 70. Inverse mapping method inheriting its configuration and ignoring some of them
@Mapper
public interface CarMapper {
@InheritInverseConfiguration
@Mapping(target = "numberOfSeats", ignore = true)
Car carDtoToCar(CarDto carDto);
}
Here the carDtoToCar() method is the reverse mapping method for carToDto(). Note that any
attribute mappings from carToDto() will be applied to the corresponding reverse mapping method
as well. They are automatically reversed and copied to the method with the
@InheritInverseConfiguration annotation.
Specific mappings from the inversed method can (optionally) be overridden by ignore, expression
or constant in the mapping, e.g. like this: @Mapping(target = "numberOfSeats", ignore=true).
A method A is considered a reverse method of a method B, if the result type of A is the same as the
single source type of B and if the single source type of A is the same as the result type of B.
Methods that are considered for inverse inheritance need to be defined in the current mapper, a
super class/interface.
If multiple methods qualify, the method from which to inherit the configuration needs to be
specified using the name property like this: @InheritInverseConfiguration(name = "carToDto").
Configurations are inherited transitively. So if method C defines a mapping @Mapping( target = "x",
ignore = true), B defines a mapping @Mapping( target = "y", ignore = true), then if A inherits from
B inherits from C, A will inherit mappings for both property x and y.
Reverse mapping of nested source properties is experimental as of the 1.1.0.Beta2 release. Reverse
mapping will take place automatically when the source property name and target property name
are identical. Otherwise, @Mapping should specify both the target name and source name. In all
cases, a suitable mapping method needs to be in place for the reverse mapping.
57
11.3. Shared configurations
MapStruct offers the possibility to define a shared configuration by pointing to a central interface
annotated with @MapperConfig. For a mapper to use the shared configuration, the configuration
interface needs to be defined in the @Mapper#config property.
The @MapperConfig annotation has the same attributes as the @Mapper annotation. Any attributes not
given via @Mapper will be inherited from the shared configuration. Attributes specified in @Mapper
take precedence over the attributes specified via the referenced configuration class. List properties
such as uses are simply combined:
@MapperConfig(
uses = CustomMapperViaMapperConfig.class,
unmappedTargetPolicy = ReportingPolicy.ERROR
)
public interface CentralConfig {
}
The interface holding the @MapperConfig annotation may also declare prototypes of mapping
methods that can be used to inherit method-level mapping annotations from. Such prototype
methods are not meant to be implemented or used as part of the mapper API.
58
Example 72. Mapper configuration class with prototype methods
@MapperConfig(
uses = CustomMapperViaMapperConfig.class,
unmappedTargetPolicy = ReportingPolicy.ERROR,
mappingInheritanceStrategy = MappingInheritanceStrategy
.AUTO_INHERIT_FROM_CONFIG
)
public interface CentralConfig {
• EXPLICIT (default): the configuration will only be inherited, if the target mapping method is
annotated with @InheritConfiguration and the source and target types are assignable to the
corresponding types of the prototype method, all as described in Mapping configuration
inheritance.
59
12. Customizing mappings
Sometimes it’s needed to apply custom logic before or after certain mapping methods. MapStruct
provides two ways for doing so: decorators which allow for a type-safe customization of specific
mapping methods and the before-mapping and after-mapping lifecycle methods which allow for a
generic customization of mapping methods with given source or target types.
When working with the component model cdi, use CDI decorators with
MapStruct mappers instead of the @DecoratedWith annotation described here.
@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
The decorator must be a sub-type of the decorated mapper type. You can make it an abstract class
which allows to only implement those methods of the mapper interface which you want to
customize. For all non-implemented methods, a simple delegation to the original mapper will be
generated using the default generation routine.
60
Example 74. Implementing a decorator
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setFullName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
The example shows how you can optionally inject a delegate with the generated default
implementation and use this delegate in your customized decorator methods.
For a mapper with componentModel = "default", define a constructor with a single parameter which
accepts the type of the decorated mapper.
When working with the component models spring or jsr330, this needs to be handled differently.
When using @DecoratedWith on a mapper with component model spring, the generated
implementation of the original mapper is annotated with the Spring annotation
@Qualifier("delegate"). To autowire that bean in your decorator, add that qualifier annotation as
well:
61
Example 75. Spring-based decorator
@Autowired
@Qualifier("delegate")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
The generated class that extends the decorator is annotated with Spring’s @Primary annotation. To
autowire the decorated mapper in the application, nothing special needs to be done:
@Autowired
private PersonMapper personMapper; // injects the decorator, with the injected
original mapper
JSR 330 doesn’t specify qualifiers and only allows to specifically name the beans. Hence, the
generated implementation of the original mapper is annotated with @Named("fully-qualified-name-
of-generated-implementation") (please note that when using a decorator, the class name of the
mapper implementation ends with an underscore). To inject that bean in your decorator, add the
same annotation to the delegate field (e.g. by copy/pasting it from the generated class):
62
Example 77. JSR 330 based decorator
@Inject
@Named("org.examples.PersonMapperImpl_")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
Unlike with the other component models, the usage site must be aware if a mapper is decorated or
not, as for decorated mappers, the parameterless @Named annotation must be added to select the
decorator to be injected:
@Inject
@Named
private PersonMapper personMapper; // injects the decorator, with the injected
original mapper
Callback methods can be implemented in the abstract mapper itself, in a type reference in
Mapper#uses, or in a type used as @Context parameter.
63
Example 79. Mapper with @BeforeMapping and @AfterMapping hooks
@Mapper
public abstract class VehicleMapper {
@BeforeMapping
protected void flushEntity(AbstractVehicle vehicle) {
// I would call my entity manager's flush() method here to make sure my
entity
// is populated with the right @Version before I let it map into the DTO
}
@AfterMapping
protected void fillTank(AbstractVehicle vehicle, @MappingTarget
AbstractVehicleDto result) {
result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType()
) );
}
if ( car == null ) {
return null;
}
return carDto;
}
}
If the @BeforeMapping / @AfterMapping method has parameters, the method invocation is only
generated if the return type of the method (if non-void) is assignable to the return type of the
mapping method and all parameters can be assigned by the source or target parameters of the
mapping method:
• A parameter annotated with @MappingTarget is populated with the target instance of the
mapping.
64
• A parameter annotated with @TargetType is populated with the target type of the mapping.
• Parameters annotated with @Context are populated with the context parameters of the mapping
method.
For non-void methods, the return value of the method invocation is returned as the result of the
mapping method if it is not null.
Example 80. Mapper with @AfterMapping hook that returns a non-null value
@Mapper
public abstract class VehicleMapper {
@PersistenceContext
private EntityManager entityManager;
@AfterMapping
protected <T> T attachEntity(@MappingTarget T entity) {
return entityManager.merge(entity);
}
return carDto;
}
}
All before/after-mapping methods that can be applied to a mapping method will be used. Mapping
65
method selection based on qualifiers can be used to further control which methods may be chosen
and which not. For that, the qualifier annotation needs to be applied to the before/after-method and
referenced in BeanMapping#qualifiedBy or IterableMapping#qualifiedBy.
1. @BeforeMapping methods without an @MappingTarget parameter are called before any null-checks
on source parameters and constructing a new target bean.
2. @BeforeMapping methods with an @MappingTarget parameter are called after constructing a new
target bean.
3. @AfterMapping methods are called at the end of the mapping method before the last return
statement.
Within those groups, the method invocations are ordered by their location of definition:
3. Methods from types referenced in Mapper#uses(), in the order of the type declaration in the
annotation.
4. Methods declared in one type are used after methods declared in their super-type.
Important: the order of methods declared within one type can not be guaranteed, as it depends on
the compiler and the processing environment implementation.
Important: when using a builder, the @AfterMapping annotated method must have the builder as
@MappingTarget annotated parameter so that the method is able to modify the object going to be
build. The build method is called when the @AfterMapping annotated method scope finishes.
MapStruct will not call the @AfterMapping annotated method if the real target is used as
@MappingTarget annotated parameter.
66
Example 81. Source object GolfPlayer with fluent API.
67
Example 82. Source object GolfPlayerDto with fluent API.
We want GolfPlayer to be mapped to a target object GolfPlayerDto similar like we 'always' do this:
@Mapper
public interface GolfPlayerMapper {
68
Example 84. CustomAccessorNamingStrategy
/**
* A custom {@link AccessorNamingStrategy} recognizing getters in the form of
{@code property()} and setters in the
* form of {@code withProperty(value)}.
*/
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
@Override
public boolean isGetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return !methodName.startsWith( "with" ) && method.getReturnType().getKind
() != TypeKind.VOID;
}
@Override
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith( "with" ) && methodName.length() > 4;
}
@Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
return IntrospectorUtils.decapitalize( methodName.startsWith( "with" ) ?
methodName.substring( 4 ) : methodName );
}
}
To use a custom SPI implementation, it must be located in a separate JAR file together with the file
META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy with the fully qualified name of
your custom implementation as content (e.g. org.mapstruct.example.CustomAccessorNamingStrategy).
This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place
where you added the mapstruct-processor jar).
Fore more details: The example above is present in our examples repository
(https://github.com/mapstruct/mapstruct-examples).
69
excluded type.
@Mapper
public interface ErroneousCustomExclusionMapper {
We want to exclude the NestedTarget from the automatic sub-mapping method generation.
70
Example 88. CustomMappingExclusionProvider
import java.util.regex.Pattern;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.spi.MappingExclusionProvider;
@Override
public boolean isExcluded(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
return name.length() != 0 && ( JAVA_JAVAX_PACKAGE.matcher( name ).matches
() ||
name.toString().equals(
"org.mapstruct.ap.test.nestedbeans.exclusions.custom.Target.NestedTarget" ) );
}
}
To use a custom SPI implementation, it must be located in a separate JAR file together with the file
META-INF/services/org.mapstruct.ap.spi.MappingExclusionProvider with the fully qualified name of
your custom implementation as content (e.g.
org.mapstruct.example.CustomMappingExclusionProvider). This JAR file needs to be added to the
annotation processor classpath (i.e. add it next to the place where you added the mapstruct-
processor jar).
import javax.lang.model.type.TypeMirror;
@Override
public BuilderInfo findBuilderInfo(TypeMirror type) {
return null;
}
}
71