Movatterモバイル変換


[0]ホーム

URL:


MapStruct 1.6.3 Reference Guide

Gunnar Morling, Andreas Gudian, Sjaak Derksen, Filip Hrisafov and the MapStruct community
2024-11-09
Table of Contents

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 MapStructGitHub Discussions to get help.

You found a typo or other error in this guide? Please let us know by opening an issue in theMapStruct GitHub repository,or, better yet, help the community and send a pull request for fixing it!

This work is licensed under theCreative Commons Attribution-ShareAlike 4.0 International License.

1. Introduction

MapStruct is a Javaannotation 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.

Compared to dynamic mapping frameworks, MapStruct offers the following advantages:

  • Fast execution by using plain method invocations instead of reflection

  • 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.

  • Clear error-reports at build time, if

    • mappings are incomplete (not all target properties are mapped)

    • mappings are incorrect (cannot find a proper mapping method or type conversion)

2. Set up

MapStruct is a Java annotation processor based onJSR 269 and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE.

It comprises the following artifacts:

  • org.mapstruct:mapstruct: contains the required annotations such as@Mapping

  • org.mapstruct:mapstruct-processor: contains the annotation processor which generates mapper implementations

2.1. Apache Maven

For Maven based projects add the following to your POM file in order to use MapStruct:

Example 1. Maven configuration
12345678910111213141516171819202122232425262728293031323334
...<properties><org.mapstruct.version>1.6.3</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.8.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>...

If you are working with the Eclipse IDE, make sure to have a current version of theM2E 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?

To double check that everything is working as expected, go to your project’s properties and select "Java Compiler" → "Annotation Processing" → "Factory Path".The MapStruct processor JAR should be listed and enabled there.Any processor options configured via the compiler plug-in (see below) should be listed under "Java Compiler" → "Annotation Processing".

If the processor is not kicking in, check that the configuration of annotation processors through M2E is enabled.To do so, go to "Preferences" → "Maven" → "Annotation Processing" and select "Automatically configure JDT APT".Alternatively, specify the following in theproperties section of your POM file:<m2e.apt.activation>jdt_apt</m2e.apt.activation>.

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:

Example 2. Gradle configuration
123456789101112131415
...plugins {    ...    id"com.diffplug.eclipse.apt" version"3.26.0"// Only for Eclipse}dependencies {    ...    implementation"org.mapstruct:mapstruct:${mapstructVersion}"    annotationProcessor"org.mapstruct:mapstruct-processor:${mapstructVersion}"// If you are using mapstruct in test code    testAnnotationProcessor"org.mapstruct:mapstruct-processor:${mapstructVersion}"}...

You can find a complete example in themapstruct-examples project on GitHub.

2.3. Apache Ant

Add thejavac task configured as follows to yourbuild.xml file in order to enable MapStruct in your Ant-based project. Adjust the paths as required for your project layout.

Example 3. Ant configuration
123456789
...<javacsrcdir="src/main/java"destdir="target/classes"classpath="path/to/mapstruct-1.6.3.jar"><compilerargline="-processorpath path/to/mapstruct-processor-1.6.3.jar"/><compilerargline="-s target/generated-sources"/></javac>...

You can find a complete example in themapstruct-examples project on GitHub.

2.4. Configuration options

The MapStruct code generator can be configured usingannotation 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 usingcompilerArgs within the configuration of the Maven processor plug-in like this:

Example 4. Maven configuration
12345678910111213141516171819202122232425262728293031
...<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><!-- due to problem in maven-compiler-plugin, for verbose mode add showWarnings --><showWarnings>true</showWarnings><compilerArgs><arg>                -Amapstruct.suppressGeneratorTimestamp=true</arg><arg>                -Amapstruct.suppressGeneratorVersionInfoComment=true</arg><arg>                -Amapstruct.verbose=true</arg></compilerArgs></configuration></plugin>...
Example 5. Gradle configuration
123456789
...compileJava {    options.compilerArgs += ['-Amapstruct.suppressGeneratorTimestamp=true','-Amapstruct.suppressGeneratorVersionInfoComment=true','-Amapstruct.verbose=true'    ]}...

The following options exist:

Table 1. MapStruct processor options
OptionPurposeDefault

mapstruct.suppressGeneratorTimestamp

If set totrue, the creation of a time stamp in the@Generated annotation in the generated mapper classes is suppressed.

false

mapstruct.verbose

If set totrue, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, alsoshowWarnings needs to be added due to a problem in the maven-compiler-plugin configuration.

false

mapstruct.suppressGeneratorVersionInfoComment

If set totrue, the creation of thecomment attribute in the@Generated annotation in the generated mapper classes is suppressed. The comment contains information about the version of MapStruct and about the compiler used for the annotation processing.

false

mapstruct.defaultComponentModel

The name of the component model (seeRetrieving a mapper) based on which mappers should be generated.

Supported values are:

  • default: the mapper uses no component model, instances are typically retrieved viaMappers#getMapper(Class)

  • cdi: the generated mapper is an application-scoped (from javax.enterprise.context or jakarta.enterprise.context, depending on which one is available with javax.inject having priority) CDI bean and can be retrieved via@Inject

  • spring: the generated mapper is a singleton-scoped Spring bean and can be retrieved via@Autowired

  • jsr330: the generated mapper is annotated with {@code @Named} and can be retrieved via@Inject (from javax.inject or jakarta.inject, depending which one is available with javax.inject having priority), e.g. using Spring

  • jakarta: the generated mapper is annotated with {@code @Named} and can be retrieved via@Inject (from jakarta.inject), e.g. using Spring

  • jakarta-cdi: the generated mapper is an application-scoped (from jakarta.enterprise.context) CDI bean and can be retrieved via@Inject

If a component model is given for a specific mapper via@Mapper#componentModel(), the value from the annotation takes precedence.

default

mapstruct.defaultInjectionStrategy

The type of the injection in mapper via parameteruses. This is only used on annotated based component models such as CDI, Spring and JSR 330.

Supported values are:

  • field: dependencies will be injected in fields

  • constructor: will be generated constructor. Dependencies will be injected via constructor.

When CDIcomponentModel a default constructor will also be generated.If a injection strategy is given for a specific mapper via@Mapper#injectionStrategy(), the value from the annotation takes precedence over the option.

field

mapstruct.unmappedTargetPolicy

The default reporting policy to be applied in case an attribute of the target object of a mapping method is not populated with a source value.

Supported values are:

  • ERROR: any unmapped target property will cause the mapping code generation to fail

  • WARN: any unmapped target property will cause a warning at build time

  • IGNORE: unmapped target properties are ignored

If a policy is given for a specific mapper via@Mapper#unmappedTargetPolicy(), the value from the annotation takes precedence.If a policy is given for a specific bean mapping via@BeanMapping#unmappedTargetPolicy(), it takes precedence over both@Mapper#unmappedTargetPolicy() and the option.

WARN

mapstruct.unmappedSourcePolicy

The default reporting policy to be applied in case an attribute of the source object of a mapping method is not populated with a target value.

Supported values are:

  • ERROR: any unmapped source property will cause the mapping code generation to fail

  • WARN: any unmapped source property will cause a warning at build time

  • IGNORE: unmapped source properties are ignored

If a policy is given for a specific mapper via@Mapper#unmappedSourcePolicy(), the value from the annotation takes precedence.If a policy is given for a specific bean mapping via@BeanMapping#ignoreUnmappedSourceProperties(), it takes precedence over both@Mapper#unmappedSourcePolicy() and the option.

IGNORE

mapstruct.disableBuilders

If set totrue, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing@Mapper( builder = @Builder( disableBuilder = true ) ) for all of your mappers.

false

mapstruct.nullValueIterableMappingStrategy

The strategy to be applied whennull is passed as a source value to an iterable mapping.

Supported values are:

  • RETURN_NULL: ifnull is passed as a source value, thennull will be returned

  • RETURN_DEFAULT: ifnull is passed then a default value (empty collection) will be returned

If a strategy is given for a specific mapper via@Mapper#nullValueIterableMappingStrategy(), the value from the annotation takes precedence.If a strategy is given for a specific iterable mapping via@IterableMapping#nullValueMappingStrategy(), it takes precedence over both@Mapper#nullValueIterableMappingStrategy() and the option.

RETURN_NULL

mapstruct.nullValueMapMappingStrategy

The strategy to be applied whennull is passed as a source value to a map mapping.

Supported values are:

  • RETURN_NULL: ifnull is passed as a source value, thennull will be returned

  • RETURN_DEFAULT: ifnull is passed then a default value (empty map) will be returned

If a strategy is given for a specific mapper via@Mapper#nullValueMapMappingStrategy(), the value from the annotation takes precedence.If a strategy is given for a specific map mapping via@MapMapping#nullValueMappingStrategy(), it takes precedence over both@Mapper#nullValueMapMappingStrategy() and the option.

RETURN_NULL

2.5. Using MapStruct with the Java Module System

MapStruct can be used with Java 9 and higher versions.

To allow usage of the@Generated annotationjava.annotation.processing.Generated (part of thejava.compiler module) can be enabled.

2.6. IDE Integration

There are optional MapStruct plugins for IntelliJ and Eclipse that allow you to have additional completion support (and more) in the annotations.

2.6.1. IntelliJ

TheMapStruct IntelliJ plugin offers assistance in projects that use MapStruct.

Some features include:

  • Code completion intarget,source,expression

  • Go To Declaration for properties intarget andsource

  • Find Usages of properties intarget andsource

  • Refactoring support

  • Errors and Quick Fixes

2.6.2. Eclipse

TheMapStruct Eclipse Plugin offers assistance in projects that use MapStruct.

Some features include:

  • Code completion intarget andsource

  • Quick Fixes

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.

3.1. Basic mappings

To create a mapper simply define a Java interface with the required mapping method(s) and annotate it with theorg.mapstruct.Mapper annotation:

Example 6. Java interface to define a mapper
12345678910
@MapperpublicinterfaceCarMapper {@Mapping(target ="manufacturer", source ="make")@Mapping(target ="seatCount", source ="numberOfSeats")    CarDto carToCarDto(Car car);@Mapping(target ="fullName", source ="name")    PersonDto personToPersonDto(Person person);}

The@Mapper annotation causes the MapStruct code generator to create an implementation of theCarMapper 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.

The property name as defined in theJavaBeans specification must be specified in the@Mapping annotation, e.g.seatCount for a property with the accessor methodsgetSeatCount() andsetSeatCount().

By means of the@BeanMapping(ignoreByDefault = true) the default behavior will beexplicit mapping, meaning that all mappings (including nested ones) have to be specified by means of the@Mapping and no warnings will be issued on missing target properties.This allows to ignore all fields, except the ones that are explicitly defined through@Mapping.

Fluent setters are also supported.Fluent setters are setters that return the same type as the type being modified.

E.g.

public Builder seatCount(int seatCount) {    this.seatCount = seatCount;    return this;}

To get a better understanding of what MapStruct does have a look at the following implementation of thecarToCarDto() method as generated by MapStruct:

Example 7. Code generated by MapStruct
1234567891011121314151617181920212223242526272829303132333435363738394041424344
// GENERATED CODEpublicclassCarMapperImplimplements CarMapper {@Overridepublic CarDto carToCarDto(Car car) {if ( car ==null ) {returnnull;        }        CarDto carDto =new CarDto();if ( car.getFeatures() !=null ) {            carDto.setFeatures(newArrayList<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;    }@Overridepublic PersonDto personToPersonDto(Person person) {//...    }private EngineDto engineToEngineDto(Engine engine) {if ( engine ==null ) {returnnull;        }        EngineDto engineDto =new EngineDto();        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 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 theprice property, see alsoImplicit type conversions)or optionally invoke / create another mapping method (as e.g. for thedriver /engine property, see alsoMapping 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 notCollection orMap 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 (seeMapping collections).

MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types.

3.2. Mapping Composition

MapStruct supports the use of meta annotations. The@Mapping annotation supports now@Target withElementType#ANNOTATION_TYPE in addition toElementType#METHOD. This allows@Mapping to be used on other (user defined) annotations for re-use purposes. For example:

12345
@Retention(RetentionPolicy.CLASS)@Mapping(target ="id", ignore =true)@Mapping(target ="creationDate", expression ="java(new java.util.Date())")@Mapping(target ="name", source ="groupName")public@interface ToEntity { }

Can be used to characterise anEntity without the need to have a common base type. For instance,ShelveEntity andBoxEntity do not share a common base type in theStorageMapper below.

12345678910111213
@MapperpublicinterfaceStorageMapper {    StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class );@ToEntity@Mapping( target ="weightLimit", source ="maxWeight")    ShelveEntity map(ShelveDto source);@ToEntity@Mapping( target ="label", source ="designation")    BoxEntity map(BoxDto source);}

Still, they do have some properties in common. The@ToEntity assumes both target beansShelveEntity andBoxEntity have properties:"id","creationDate" and"name". It furthermore assumes that the source beansShelveDto andBoxDto always have a property"groupName". This concept is also known as "duck-typing". In other words, if it quacks like duck, walks like a duck its probably a duck.

Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the@Mapping annotation. However, the composition aspect is not visible. The messages are "as if" the@Mapping would be present on the concerned method directly.Therefore, the user should use this feature with care, especially when uncertain when a property is always present.

A more typesafe (but also more verbose) way would be to define base classes / interfaces on the target bean and the source bean and use@InheritConfiguration to achieve the same result (seeMapping configuration inheritance).

3.3. Adding custom methods to mappers

In some cases it can be required to manually implement a specific mapping from one type to another which can’t be generated by MapStruct. One way to handle this is to implement the custom method on another class which then is used by mappers generated by MapStruct (seeInvoking other mappers).

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 fromPerson toPersonDto requires some special logic which can’t be generated by MapStruct. You could then define the mapper from the previous example like this:

Example 8. Mapper which defines a custom mapping with a default method
1234567891011
@MapperpublicinterfaceCarMapper {@Mapping(...)    ...    CarDto carToCarDto(Car car);default PersonDto personToPersonDto(Person person) {//hand-written mapping logic    }}

The class generated by MapStruct implements the methodcarToCarDto(). The generated code incarToCarDto() will invoke the manually implementedpersonToPersonDto() method when mapping thedriver 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 fromPerson toPersonDto requires some special logic could then be defined like this:

Example 9. Mapper defined by an abstract class
1234567891011
@MapperpublicabstractclassCarMapper {@Mapping(...)    ...    publicabstract CarDto carToCarDto(Car car);public PersonDto personToPersonDto(Person person) {//hand-written mapping logic    }}

MapStruct will generate a sub-class ofCarMapper with an implementation of thecarToCarDto() method as it is declared abstract. The generated code incarToCarDto() will invoke the manually implementedpersonToPersonDto() method when mapping thedriver attribute.

3.4. Mapping methods with several source parameters

MapStruct also supports mapping methods with several source parameters. This is useful e.g. in order to combine several entities into one data transfer object. The following shows an example:

Example 10. Mapping method with several source parameters
1234567
@MapperpublicinterfaceAddressMapper {@Mapping(target ="description", source ="person.description")@Mapping(target ="houseNumber", source ="address.houseNo")    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);}

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 thedescription 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 returnnull in case all the source parameters arenull. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated.

MapStruct also offers the possibility to directly refer to a source parameter.

Example 11. Mapping method directly referring to a source parameter
1234567
@MapperpublicinterfaceAddressMapper {@Mapping(target ="description", source ="person.description")@Mapping(target ="houseNumber", source ="hn")    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person,Integer hn);}

In this case the source parameter is directly mapped into the target as the example above demonstrates. The parameterhn, a non bean type (in this casejava.lang.Integer) is mapped tohouseNumber.

3.5. Mapping nested bean properties to current target

If you don’t want explicitly name all properties from nested source bean, you can use. as target. This will tell MapStruct to map every property from source bean to target object. The following shows an example:

Example 12. use of "target this" annotation "."
12345678
@MapperpublicinterfaceCustomerMapper {@Mapping( target ="name", source ="record.name" )@Mapping( target =".", source ="record" )@Mapping( target =".", source ="account" )     Customer customerDtoToCustomer(CustomerDto customerDto); }

The generated code will map every property fromCustomerDto.record toCustomer directly, without need to manually name any of them.The same goes forCustomer.account.

When there are conflicts, these can be resolved by explicitely defining the mapping. For instance in the example above.name occurs inCustomerDto.record and inCustomerDto.account. The mapping@Mapping( target = "name", source = "record.name" ) resolves this conflict.

This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (@InheritInverseConfiguration).

3.6. Updating existing bean instances

In some cases you need mappings which don’t create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with@MappingTarget. The following shows an example:

Example 13. Update method
12345
@MapperpublicinterfaceCarMapper {void updateCarFromDto(CarDto carDto,@MappingTarget Car car);}

The generated code of theupdateCarFromDto() method will update the passedCar instance with the properties from the givenCarDto object. There may be only one parameter marked as mapping target. Instead ofvoid 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.

ForCollectionMappingStrategy.ACCESSOR_ONLY Collection- or map-typed properties of the target bean to be updated will be cleared and then populated with the values from the corresponding source collection or map. Otherwise, ForCollectionMappingStrategy.ADDER_PREFERRED orCollectionMappingStrategy.TARGET_IMMUTABLE the target will not be cleared and the values will be populated immediately.

3.7. Mappings with direct field access

MapStruct also supports mappings ofpublic fields that have no getters/setters. MapStruct willuse the fields as read/write accessor if it cannot find suitable getter/setter methods for the property.

A field is considered as a read accessor if it ispublic orpublic final. If a field isstatic it is notconsidered as a read accessor.

A field is considered as a write accessor only if it ispublic. If a field isfinal and/orstatic it is notconsidered as a write accessor.

Small example:

Example 14. Example classes for mapping
12345678910111213141516171819202122232425
publicclassCustomer {privateLong id;privateString name;//getters and setter omitted for brevity}publicclassCustomerDto {publicLong id;publicString customerName;}@MapperpublicinterfaceCustomerMapper {    CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );@Mapping(target ="name", source ="customerName")    Customer toCustomer(CustomerDto customerDto);@InheritInverseConfiguration    CustomerDto fromCustomer(Customer customer);}

For the configuration from above, the generated mapper looks like:

Example 15. Generated mapper for example classes
12345678910111213141516171819
// GENERATED CODEpublicclassCustomerMapperImplimplements CustomerMapper {@Overridepublic Customer toCustomer(CustomerDto customerDto) {// ...        customer.setId( customerDto.id );        customer.setName( customerDto.customerName );// ...    }@Overridepublic CustomerDto fromCustomer(Customer customer) {// ...        customerDto.id = customer.getId();        customerDto.customerName = customer.getName();// ...    }}

You can find the complete example in themapstruct-examples-field-mappingproject on GitHub.

3.8. Using builders

MapStruct also supports mapping of immutable types via builders.When performing a mapping MapStruct checks if there is a builder for the type being mapped.This is done via theBuilderProvider SPI.If a Builder exists for a certain type, then that builder will be used for the mappings.

The default implementation of theBuilderProvider assumes the following:

  • The type has a parameterless public static builder creation method that returns a builder.So for examplePerson has a public static method that returnsPersonBuilder.

  • The builder type has a parameterless public method (build method) that returns the type being built.In our examplePersonBuilder has a method returningPerson.

  • In case there are multiple build methods, MapStruct will look for a method calledbuild, if such method existsthen 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 aMoreThanOneBuilderCreationMethodExceptionwill be thrown from theDefaultBuilderProvider SPI.In case of aMoreThanOneBuilderCreationMethodException MapStruct will write a warning in the compilation and not use any builder.

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.

Builder detection can be switched off by means of@Builder#disableBuilder. MapStruct will fall back on regular getters / setters in case builders are disabled.

TheObject factories are also considered for the builder type.E.g. If an object factory exists for ourPersonBuilder then this factory would be used instead of the builder creation method.

Detected builders influence@BeforeMapping and@AfterMapping behavior. SeeMapping customization with before-mapping and after-mapping methods for more information.

Example 16. Person with Builder example
1234567891011121314151617181920212223242526
publicclassPerson {privatefinalString name;protected Person(Person.Builder builder) {this.name = builder.name;    }publicstatic Person.Builder builder() {returnnew Person.Builder();    }publicstaticclassBuilder {privateString name;public Builder name(String name) {this.name = name;returnthis;        }public Person create() {returnnew Person(this );        }    }}
Example 17. Person Mapper definition
1234
publicinterfacePersonMapper {    Person map(PersonDto dto);}
Example 18. Generated mapper with builder
123456789101112131415
// GENERATED CODEpublicclassPersonMapperImplimplements PersonMapper {public Person map(PersonDto dto) {if (dto ==null) {returnnull;        }        Person.Builder builder = Person.builder();        builder.name( dto.getName() );return builder.create();    }}

Supported builder frameworks:

  • Lombok - It is required to have the Lombok classes in a separate module.See for more information atrzwitserloot/lombok#1538 and to set up Lombok with MapStruct, refer toLombok.

  • AutoValue

  • Immutables - When Immutables are present on the annotation processor path then theImmutablesAccessorNamingStrategy andImmutablesBuilderProvider would be used by default

  • FreeBuilder - When FreeBuilder is present on the annotation processor path then theFreeBuilderAccessorNamingStrategy 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 defaultBuilderProvider.Otherwise, you would need to write a customBuilderProvider

In case you want to disable using builders then you can pass the MapStruct processor optionmapstruct.disableBuilders to the compiler. e.g.-Amapstruct.disableBuilders=true.

3.9. Using Constructors

MapStruct supports using constructors for mapping target types.When doing a mapping MapStruct checks if there is a builder for the type being mapped.If there is no builder, then MapStruct looks for a single accessible constructor.When there are multiple constructors then the following is done to pick the one which should be used:

  • If a constructor is annotated with an annotationnamed@Default (from any package, seeNon-shipped annotations) it will be used.

  • If a single public constructor exists then it will be used to construct the object, and the other non public constructors will be ignored.

  • If a parameterless constructor exists then it will be used to construct the object, and the other constructors will be ignored.

  • If there are multiple eligible constructors then there will be a compilation error due to ambiguous constructors. In order to break the ambiguity an annotationnamed@Default (from any package, seeNon-shipped annotations) can used.

Example 19. Deciding which constructor to use
12345678910111213141516171819202122232425262728293031323334
publicclassVehicle {protected Vehicle() { }// MapStruct will use this constructor, because it is a single public constructorpublic Vehicle(String color) { }}publicclassCar {// MapStruct will use this constructor, because it is a parameterless empty constructorpublic Car() { }public Car(String make,String color) { }}publicclassTruck {public Truck() { }// MapStruct will use this constructor, because it is annotated with @Default@Defaultpublic Truck(String make,String color) { }}publicclassVan {// There will be a compilation error when using this class because MapStruct cannot pick a constructorpublic Van(String make) { }public Van(String make,String color) { }}

When using a constructor then the names of the parameters of the constructor will be used and matched to the target properties.When the constructor has an annotationnamed@ConstructorProperties (from any package, seeNon-shipped annotations) then this annotation will be used to get the names of the parameters.

When an object factory method or a method annotated with@ObjectFactory exists, it will take precedence over any constructor defined in the target.The target object constructor will not be used in that case.

Example 20. Person with constructor parameters
12345678910
publicclassPerson {privatefinalString name;privatefinalString surname;public Person(String name,String surname) {this.name = name;this.surname = surname;    }}
Example 21. Person With Constructor Mapper definition
1234
publicinterfacePersonMapper {    Person map(PersonDto dto);}
Example 22. Generated mapper with constructor
123456789101112131415161718
// GENERATED CODEpublicclassPersonMapperImplimplements PersonMapper {public Person map(PersonDto dto) {if (dto ==null) {returnnull;        }String name;String surname;        name = dto.getName();        surname = dto.getSurname();        Person person =new Person( name, surname );return person;    }}

3.10. Mapping Map to Bean

There are situations when a mapping from aMap<String, ???> into a specific bean is needed.MapStruct offers a transparent way of doing such a mapping by using the target bean properties (or defined throughMapping#source) to extract the values from the map.Such a mapping looks like:

Example 23. Example classes for mapping map to bean
123456789101112131415
publicclassCustomer {privateLong id;privateString name;//getters and setter omitted for brevity}@MapperpublicinterfaceCustomerMapper {@Mapping(target ="name", source ="customerName")    Customer toCustomer(Map<String,String> map);}
Example 24. Generated mapper for mapping map to bean
123456789101112131415
// GENERATED CODEpublicclassCustomerMapperImplimplements CustomerMapper {@Overridepublic Customer toCustomer(Map<String,String> map) {// ...if ( map.containsKey("id" ) ) {            customer.setId(Integer.parseInt( map.get("id" ) ) );        }if ( map.containsKey("customerName" ) ) {            customer.setName( map.get("customerName" ) );        }// ...    }}

All existing rules about mapping between different types and using other mappers defined withMapper#uses or custom methods in the mappers are applied.i.e. You can map fromMap<String, Integer> where for each property a conversion fromInteger into the respective property will be needed.

When a raw map or a map that does not have a String as a key is used, then a warning will be generated.The warning is not generated if the map itself is mapped into some other target property directly as is.

3.11. Adding annotations

Other frameworks sometimes requires you to add annotations to certain classes so that they can easily detect the mappers.Using the@AnnotateWith annotation you can generate an annotation at the specified location.

For example Apache Camel has a@Converter annotation which you can apply to generated mappers using the@AnnotateWith annotation.

Example 25. AnnotateWith source example
123456789
@Mapper@AnnotateWith(  value = Converter.class,  elements =@AnnotateWith.Element( name ="generateBulkLoader", booleans =true ))publicinterfaceMyConverter {@AnnotateWith( Converter.class )    DomainObject map( DtoObject dto );}
Example 26. AnnotateWith generated mapper
1234567
@Converter( generateBulkLoader =true )publicclassMyConverterImplimplements MyConverter {@Converterpublic DomainObject map( DtoObject dto ) {// default mapping behaviour    }}

3.12. Adding Javadoc comments

MapStruct provides support for defining Javadoc comments in the generated mapper implementation using theorg.mapstruct.Javadoc annotation.

This functionality could be relevant especially in situations where certain Javadoc standards need to be met orto deal with Javadoc validation constraints.

The@Javadoc annotation defines attributes for the different Javadoc elements.

Consider the following example:

Example 27. Javadoc annotation example
12345678910
@Mapper@Javadoc(    value ="This is the description",    authors = {"author1","author2" },    deprecated ="Use {@link OtherMapper} instead",    since ="0.1")publicinterfaceMyAnnotatedWithJavadocMapper {//...}
Example 28. Javadoc annotated generated mapper
123456789101112
/*** This is the description** @author author1* @author author2** @deprecated Use {@link OtherMapper} instead* @since 0.1*/publicclassMyAnnotatedWithJavadocMapperImplimplements MyAnnotatedWithJavadocMapper {//...}

The entire Javadoc comment block can be provided directly as well.

Example 29. Javadoc annotation example with the entire Javadoc comment block provided directly
12345678910111213
@Mapper@Javadoc("This is the description\n"  +"\n"  +"@author author1\n"  +"@author author2\n"  +"\n"  +"@deprecated Use {@link OtherMapper} instead\n"  +"@since 0.1\n")publicinterfaceMyAnnotatedWithJavadocMapper {//...}

Or using Text blocks:

Example 30. Javadoc annotation example with the entire Javadoc comment block provided directly using Text blocks
123456789101112131415
@Mapper@Javadoc("""    This is the description    @author author1    @author author2    @deprecated Use {@link OtherMapper} instead    @since 0.1""")publicinterfaceMyAnnotatedWithJavadocMapper {//...}

4. Retrieving a mapper

4.1. The Mappers factory (no dependency injection)

When not using a DI framework, Mapper instances can be retrieved via theorg.mapstruct.factory.Mappers class. Just invoke thegetMapper() method, passing the interface type of the mapper to return:

Example 31. Using the Mappers factory
1
CarMapper mapper = Mappers.getMapper( CarMapper.class );

By convention, a mapper interface should define a member calledINSTANCE which holds a single instance of the mapper type:

Example 32. Declaring an instance of a mapper (interface)
1234567
@MapperpublicinterfaceCarMapper {    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );    CarDto carToCarDto(Car car);}
Example 33. Declaring an instance of a mapper (abstract class)
1234567
@MapperpublicabstractclassCarMapper {publicstaticfinal CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );    CarDto carToCarDto(Car car);}

This pattern makes it very easy for clients to use mapper objects without repeatedly instantiating new instances:

Example 34. Accessing a mapper
12
Car car = ...;CarDto dto = CarMapper.INSTANCE.carToCarDto( car );

Note that mappers generated by MapStruct are stateless and thread-safe and thus can safely be accessed from several threads at the same time.

4.2. Using dependency injection

If you’re working with a dependency injection framework such asCDI (Contexts and Dependency Injection for JavaTM EE) or theSpring Framework, it is recommended to obtain mapper objects via dependency injection andnot via theMappers 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 inConfiguration options.

Currently there is support for CDI and Spring (the latter either via its custom annotations or using the JSR 330 annotations). SeeConfiguration options for the allowed values of thecomponentModel attribute which are the same as for themapstruct.defaultComponentModel processor option and constants are defined in a classMappingConstants.ComponentModel. 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:

Example 35. A mapper using the CDI component model
12345
@Mapper(componentModel = MappingConstants.ComponentModel.CDI)publicinterfaceCarMapper {    CarDto carToCarDto(Car car);}

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:

Example 36. Obtaining a mapper via dependency injection
12
@Injectprivate CarMapper mapper;

A mapper which uses other mapper classes (seeInvoking other mappers) will obtain these mappers using the configured component model. So ifCarMapper from the previous example was using another mapper, this other mapper would have to be an injectable CDI bean as well.

4.3. Injection strategy

When usingdependency injection, you can choose between constructor, field, or setter injection.This can be done by either providing the injection strategy via@Mapper or@MapperConfig annotation.

Example 37. Using constructor injection
1234
@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)publicinterfaceCarMapper {    CarDto carToCarDto(Car car);}

The generated mapper will inject classes defined in theuses attribute if MapStruct has detected that it needs to use an instance of it for a mapping.WhenInjectionStrategy#CONSTRUCTOR is used, the constructor will have the appropriate annotation and the fields won’t.WhenInjectionStrategy#FIELD is used, the annotation is on the field itself.WhenInjectionStrategy#SETTER is used the annotation is on a generated setter method.For now, the default injection strategy is field injection, but it can be configured withConfiguration options.It is recommended to use constructor injection to simplify testing.

When you define mappers in Spring with circular dependencies compilation may fail.In that case utilize theInjectionStrategy#SETTER strategy.

For abstract classes or decorators setter injection should be used.

5. Data type conversions

Not always a mapped attribute has the same type in the source and target objects. For instance an attribute may be of typeint in the source bean but of typeLong in the target bean.

Another example are references to other objects which should be mapped to the corresponding types in the target model. E.g. the classCar might have a propertydriver of the typePerson which needs to be converted into aPersonDto object when mapping aCar object.

In this section you’ll learn how MapStruct deals with such data type conversions.

5.1. Implicit type conversions

MapStruct takes care of type conversions automatically in many cases. If for instance an attribute is of typeint in the source bean but of typeString in the target bean, the generated code will transparently perform a conversion by callingString#valueOf(int) andInteger#parseInt(String), respectively.

Currently the following conversions are applied automatically:

  • Between all Java primitive data types and their corresponding wrapper types, e.g. betweenint andInteger,boolean andBoolean etc. The generated code isnull aware, i.e. when converting a wrapper type into the corresponding primitive type anull check will be performed.

  • Between all Java primitive number types and the wrapper types, e.g. betweenint andlong orbyte andInteger.

Converting from larger data types to smaller ones (e.g. fromlong toint) can cause a value or precision loss. TheMapper andMapperConfig annotations have a methodtypeConversionPolicy to control warnings / errors. Due to backward compatibility reasons the default value isReportingPolicy.IGNORE.

  • Between all Java primitive types (including their wrappers) andString, e.g. betweenint andString orBoolean andString. A format string as understood byjava.text.DecimalFormat can be specified.

Example 38. Conversion from int to String
123456789
@MapperpublicinterfaceCarMapper {@Mapping(source ="price", numberFormat ="$#.00")    CarDto carToCarDto(Car car);@IterableMapping(numberFormat ="$#.00")List<String> prices(List<Integer> prices);}
  • Betweenenum types andString.

  • Betweenenum types andInteger, according toenum.ordinal().

    • When converting from anInteger, the value needs to be less than the number of values of the enum, otherwise anArrayOutOfBoundsException is thrown.

  • Between big number types (java.math.BigInteger,java.math.BigDecimal) and Java primitive types (including their wrappers) as well as String. A format string as understood byjava.text.DecimalFormat can be specified.

Example 39. Conversion from BigDecimal to String
1234567
@MapperpublicinterfaceCarMapper {@Mapping(source ="power", numberFormat ="#.##E0")    CarDto carToCarDto(Car car);}
  • BetweenJAXBElement<T> andT,List<JAXBElement<T>> andList<T>

  • Betweenjava.util.Calendar/java.util.Date and JAXB’sXMLGregorianCalendar

  • Betweenjava.util.Date/XMLGregorianCalendar andString. A format string as understood byjava.text.SimpleDateFormat can be specified via thedateFormat option as this:

Example 40. Conversion from Date to String
123456789
@MapperpublicinterfaceCarMapper {@Mapping(source ="manufacturingDate", dateFormat ="dd.MM.yyyy")    CarDto carToCarDto(Car car);@IterableMapping(dateFormat ="dd.MM.yyyy")List<String> stringListToDateList(List<Date> dates);}
  • Between Jodasorg.joda.time.DateTime,org.joda.time.LocalDateTime,org.joda.time.LocalDate,org.joda.time.LocalTime andString. A format string as understood byjava.text.SimpleDateFormat can be specified via thedateFormat option (see above).

  • Between Jodasorg.joda.time.DateTime andjavax.xml.datatype.XMLGregorianCalendar,java.util.Calendar.

  • Between Jodasorg.joda.time.LocalDateTime,org.joda.time.LocalDate andjavax.xml.datatype.XMLGregorianCalendar,java.util.Date.

  • Betweenjava.time.LocalDate,java.time.LocalDateTime andjavax.xml.datatype.XMLGregorianCalendar.

  • Betweenjava.time.ZonedDateTime,java.time.LocalDateTime,java.time.LocalDate,java.time.LocalTime from Java 8 Date-Time package andString. A format string as understood byjava.text.SimpleDateFormat can be specified via thedateFormat option (see above).

  • Betweenjava.time.Instant,java.time.Duration,java.time.Period from Java 8 Date-Time package andString using theparse method in each class to map fromString and usingtoString to map intoString.

  • Betweenjava.time.ZonedDateTime from Java 8 Date-Time package andjava.util.Date where, when mapping aZonedDateTime from a givenDate, the system default timezone is used.

  • Betweenjava.time.LocalDateTime from Java 8 Date-Time package andjava.util.Date where timezone UTC is used as the timezone.

  • Betweenjava.time.LocalDate from Java 8 Date-Time package andjava.util.Date /java.sql.Date where timezone UTC is used as the timezone.

  • Betweenjava.time.Instant from Java 8 Date-Time package andjava.util.Date.

  • Betweenjava.time.LocalDateTime from Java 8 Date-Time package andjava.time.LocalDate from the same package.

  • Betweenjava.time.ZonedDateTime from Java 8 Date-Time package andjava.util.Calendar.

  • Betweenjava.sql.Date andjava.util.Date

  • Betweenjava.sql.Time andjava.util.Date

  • Betweenjava.sql.Timestamp andjava.util.Date

  • When converting from aString, omittingMapping#dateFormat, it leads to usage of the default pattern and date format symbols for the default locale. An exception to this rule isXmlGregorianCalendar which results in parsing theString according toXML Schema 1.0 Part 2, Section 3.2.7-14.1, Lexical Representation.

  • Betweenjava.util.Currency andString.

    • When converting from aString, the value needs to be a validISO-4217 alphabetic code otherwise anIllegalArgumentException is thrown.

  • Betweenjava.util.UUID andString.

    • When converting from aString, the value needs to be a validUUID otherwise anIllegalArgumentException is thrown.

  • BetweenString andStringBuilder

  • Betweenjava.net.URL andString.

    • When converting from aString, the value needs to be a validURL otherwise aMalformedURLException is thrown.

  • Betweenjava.util.Locale andString.

    • When converting from aLocale, the resultingString will be a well-formed IETF BCP 47 language tag representing the locale. When converting from aString, the locale that best represents the language tag will be returned. SeeLocale.forLanguageTag() andLocale.toLanguageTag() for more information.

5.2. Mapping object references

Typically an object has not only primitive attributes but also references other objects. E.g. theCar class could contain a reference to aPerson object (representing the car’s driver) which should be mapped to aPersonDto object referenced by theCarDto class.

In this case just define a mapping method for the referenced object type as well:

Example 41. Mapper with one mapping method using another
1234567
@MapperpublicinterfaceCarMapper {    CarDto carToCarDto(Car car);    PersonDto personToPersonDto(Person person);}

The generated code for thecarToCarDto() method will invoke thepersonToPersonDto() method for mapping thedriver attribute, while the generated implementation forpersonToPersonDto() 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:

  1. If source and target attribute have the same type, the value will be simply copieddirect from source to target. If the attribute is a collection (e.g. aList) a copy of the collection will be set into the target attribute.

  2. If source and target attribute type differ, check whether there is anothermapping 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.

  3. If no such method exists MapStruct will look whether abuilt-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.

  4. If no such method exists MapStruct will applycomplex conversions:

    1. mapping method, the result mapped by mapping method, like this:target = method1( method2( source ) )

    2. built-in conversion, the result mapped by mapping method, like this:target = method( conversion( source ) )

    3. mapping method, the result mapped by build-in conversion, like this:target = conversion( method( source ) )

  5. 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.

  6. 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.

A mapping control (MappingControl) can be defined on all levels (@MapperConfig,@Mapper,@BeanMapping,@Mapping), the latter taking precedence over the former. For example:@Mapper( mappingControl = NoComplexMapping.class ) takes precedence over@MapperConfig( mappingControl = DeepClone.class ).@IterableMapping and@MapMapping work similar as@Mapping. MappingControl is experimental from MapStruct 1.4.MappingControl has an enum that corresponds to the first 4 options above:MappingControl.Use#DIRECT,MappingControl.Use#MAPPING_METHOD,MappingControl.Use#BUILT_IN_CONVERSION andMappingControl.Use#COMPLEX_MAPPING the presence of which allows the user to switchon a option. The absence of an enum switchesoff a mapping option. Default they are all present enabling all mapping options.

In order to stop MapStruct from generating automatic sub-mapping methods as in 5. above, one can use@Mapper( disableSubMappingMethodsGeneration = true ).

The user has full control over the mapping by means of meta annotations. Some handy ones have been defined such as@DeepClone which only allows direct mappings. The result: if source and target type are the same, MapStruct will make a deep clone of the source. Sub-mappings-methods have to be allowed (default option).

During the generation of automatic sub-mapping methodsShared configurations will not be taken into consideration, yet.Follow issue#1086 for more information.

Constructor properties of the target object are also considered as target properties.You can read more about that inUsing Constructors

5.3. Controlling nested bean mappings

As explained above, MapStruct will generate a method based on the name of the source and target property. Unfortunately, in many occasions these names do not match.

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 elaborateexample 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 propertyfish which has an identical name inFishTankDto andFishTank.For this property MapStruct automatically generates a mapping:FishDto fishToFishDto(Fish fish).MapStruct cannot possibly be aware of the deviating propertieskind andtype.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 namekind at this level and map it totype.

Example 42. Mapper controlling nested beans mappings I
12345678910
@MapperpublicinterfaceFishTankMapper {@Mapping(target ="fish.kind", source ="fish.type")@Mapping(target ="fish.name", ignore =true)@Mapping(target ="ornament", source ="interior.ornament")@Mapping(target ="material.materialType", source ="material")@Mapping(target ="quality.report.organisation.name", source ="quality.report.organisationName")    FishTankDto map( FishTank source );}

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", 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 ofQuality are mapped toQualityDto.Likewise, all properties ofReport are mapped toReportDto, with one exception:organisation inOrganisationDto is left empty (since there is no organization at the source level).Only thename is populated with theorganisationName fromReport.This is demonstrated in@Mapping(target="quality.report.organisation.name", source="quality.report.organisationName")

Coming back to the original example: what ifkind andtype would be beans themselves?In that case MapStruct would again generate a method continuing to map.Such is demonstrated in the next example:

Example 43. Mapper controlling nested beans mappings II
12345678910111213
@MapperpublicinterfaceFishTankMapperWithDocument {@Mapping(target ="fish.kind", source ="fish.type")@Mapping(target ="fish.name", expression ="java(\"Jaws\")")@Mapping(target ="plant", ignore =true )@Mapping(target ="ornament", ignore =true )@Mapping(target ="material", ignore =true)@Mapping(target ="quality.document", source ="quality.report")@Mapping(target ="quality.document.organisation.name", constant ="NoIdeaInc" )    FishTankWithNestedDocumentDto map( FishTank source );}

Note what happens in@Mapping(target="quality.document", source="quality.report").DocumentDto does not exist as such on the target side. It is mapped fromReport.MapStruct continues to generate mapping code here. That mapping itself can be guided towards another name.This even works for constants and expression. Which is shown in the final example:@Mapping(target="quality.document.organisation.name", constant="NoIdeaInc").

MapStruct will perform a null check on each nested property in the source.

Instead of configuring everything via the parent method we encourage users to explicitly write their own nested methods.This puts the configuration of the nested mapping into one place (method) where it can be reused from several methods in the upper level,instead of re-configuring the same things on all of those upper methods.

In some cases theReportingPolicy that is going to be used for the generated nested method would beIGNORE.This means that it is possible for MapStruct not to report unmapped target properties in nested mappings.

5.4. Invoking custom mapping method

Sometimes mappings are not straightforward and some fields require custom logic.

The example below demonstrates how the propertieslength,width andheight inFishTank can be mapped to theVolumeDto bean, which is a member ofFishTankWithVolumeDto.VolumeDto contains the propertiesvolume anddescription. Custom logic is achieved by defining a method which takesFishTank instance as a parameter and returns aVolumeDto. MapStruct will take the entire parametersource and generate code to call the custom methodmapVolume in order to map theFishTank object to the target propertyvolume.

The remainder of the fields could be mapped the regular way: using mappings defined defined by means of@Mapping annotations.

Example 44. Manually implemented mapping method
123456789101112131415161718192021222324252627282930313233343536
publicclassFishTank {    Fish fish;String material;    Quality quality;int length;int width;int height;}publicclassFishTankWithVolumeDto {    FishDto fish;    MaterialDto material;    QualityDto quality;    VolumeDto volume;}publicclassVolumeDto {int volume;String description;}@MapperpublicabstractclassFishTankMapperWithVolume {@Mapping(target ="fish.kind", source ="source.fish.type")@Mapping(target ="material.materialType", source ="source.material")@Mapping(target ="quality.document", source ="source.quality.report")@Mapping(target ="volume", source ="source")abstract FishTankWithVolumeDto map(FishTank source);    VolumeDto mapVolume(FishTank source) {int volume = source.length * source.width * source.height;String desc = volume <100 ?"Small" :"Large";returnnew VolumeDto(volume, desc);    }}

Note the@Mapping annotation wheresource field is equal to"source", indicating the parameter namesource itself in the methodmap(FishTank source) instead of a (target) property inFishTank.

5.5. 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 theCar class might contain an attributemanufacturingDate while the corresponding DTO attribute is of type String. In order to map this attribute, you could implement a mapper class like this:

Example 45. Manually implemented mapper class
1234567891011121314151617
publicclassDateMapper {publicString asString(Date date) {return date !=null ?newSimpleDateFormat("yyyy-MM-dd" )            .format( date ) :null;    }publicDate asDate(String date) {try {return date !=null ?newSimpleDateFormat("yyyy-MM-dd" )                .parse( date ) :null;        }catch (ParseException e ) {thrownewRuntimeException( e );        }    }}

In the@Mapper annotation at theCarMapper interface reference theDateMapper class like this:

Example 46. Referencing another mapper class
12345
@Mapper(uses=DateMapper.class)publicinterfaceCarMapper {    CarDto carToCarDto(Car car);}

When generating code for the implementation of thecarToCarDto() method, MapStruct will look for a method which maps aDate object into a String, find it on theDateMapper class and generate an invocation ofasString() for mapping themanufacturingDate attribute.

Generated mappers retrieve referenced mappers using the component model configured for them. If e.g. CDI was used as component model forCarMapper,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.

5.6. Passing the mapping target type to custom mappers

When having a custom mapper hooked into the generated mapper with@Mapper#uses(), an additional parameter of typeClass (or a super-type of it) can be defined in the custom mapping method in order to perform general mapping tasks for specific target object types. That attribute must be annotated with@TargetType for MapStruct to generate calls that pass theClass instance representing the corresponding property type of the target bean.

For instance, theCarDto could have a propertyowner of typeReference that contains the primary key of aPerson entity. You could now create a generic custom mapper that resolves anyReference objects to their corresponding managed JPA entity instances.

e.g.

Example 47. Example classes for the passing target type example
12345678910111213141516171819202122
publicclassCar {private Person owner;// ...}publicclassPersonextends BaseEntity {// ...}publicclassReference {privateString pk;// ...}publicclassCarDto {privateReference owner;// ...}
Example 48. Mapping method expecting mapping target type as parameter
1234567891011121314151617181920
@ApplicationScoped// CDI component modelpublicclassReferenceMapper {@PersistenceContextprivate EntityManager entityManager;public <Textends BaseEntity> T resolve(Reference reference,@TargetTypeClass<T> entityClass) {return reference !=null ? entityManager.find( entityClass, reference.getPk() ) :null;    }publicReference toReference(BaseEntity entity) {return entity !=null ?newReference( entity.getPk() ) :null;    }}@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = ReferenceMapper.class )publicinterfaceCarMapper {    Car carDtoToCar(CarDto carDto);}

MapStruct will then generate something like this:

Example 49. Generated code
123456789101112131415161718192021
//GENERATED CODE@ApplicationScopedpublicclassCarMapperImplimplements CarMapper {@Injectprivate ReferenceMapper referenceMapper;@Overridepublic Car carDtoToCar(CarDto carDto) {if ( carDto ==null ) {returnnull;        }        Car car =new Car();        car.setOwner( referenceMapper.resolve( carDto.getOwner(),Owner.class ) );// ...return car;    }}

5.7. Passing context or state objects to custom methods

Additionalcontext orstate information can be passed through generated mapping methods to custom methods with@Context parameters. Such parameters are passed to other mapping methods,@ObjectFactory methods (seeObject factories) or@BeforeMapping /@AfterMapping methods (seeMapping customization with before-mapping and after-mapping methods) when applicable and can thus be used in custom code.

@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: nonull checks are performed before calling before/after mapping methods on context parameters. The caller needs to make sure thatnull 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 literalnull instead.

Example 50. Using@Context parameters for passing data down to hand-written property mapping methods
12345
publicabstract CarDto toCar(Car car,@ContextLocale translationLocale);protected OwnerManualDto translateOwnerManual(OwnerManual ownerManual,@ContextLocale locale) {// manually implemented logic to translate the OwnerManual with the given Locale}

MapStruct will then generate something like this:

Example 51. Generated code
12345678910111213
//GENERATED CODEpublic CarDto toCar(Car car,Locale translationLocale) {if ( car ==null ) {returnnull;    }    CarDto carDto =new CarDto();    carDto.setOwnerManual( translateOwnerManual( car.getOwnerManual(), translationLocale );// more generated mapping codereturn carDto;}

5.8. Mapping method resolution

When mapping a property from one type to another, MapStruct looks for the most specific method which maps the source type into the target type. The method may either be declared on the same mapper interface or on another mapper which is registered via@Mapper#uses(). The same applies for factory methods (seeObject factories).

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.

When working with JAXB, e.g. when converting aString to a correspondingJAXBElement<String>, MapStruct will take thescope andname attributes of@XmlElementDecl annotations into account when looking for a mapping method. This makes sure that the createdJAXBElement instances will have the right QNAME value. You can find a test which maps JAXB objectshere.

5.9. Mapping method selection based on qualifiers

In many occasions one requires mapping methods with the same method signature (apart from the name) that have different behavior.MapStruct has a handy mechanism to deal with such situations:@Qualifier (org.mapstruct.Qualifier).A ‘qualifier’ is a custom annotation that the user can write, ‘stick onto’ a mapping method which is included as used mapperand can be referred to in a bean property mapping, iterable mapping or map mapping.Multiple qualifiers can be ‘stuck onto’ a method and mapping.

So, let’s say there is a hand-written method to map titles with aString return type andString argument amongst many other referenced mappers with the sameString return type -String argument signature:

Example 52. Several mapping methods with identical source and target types
12345678910
publicclassTitles {publicString translateTitleEG(String title) {// some mapping logic    }publicString translateTitleGE(String title) {// some mapping logic    }}

And a mapper using this handwritten mapper, in which source and target have a property 'title' that should be mapped:

Example 53. Mapper causing an ambiguous mapping method error
123456
@Mapper( uses = Titles.class )publicinterfaceMovieMapper {     GermanRelease toGerman( OriginalRelease movies );}

Without the use of qualifiers, this would result in an ambiguous mapping method error, because 2 qualifying methods are found (translateTitleEG,translateTitleGE) and MapStruct would not have a hint which one to choose.

Enter the qualifier approach:

Example 54. Declaring a qualifier type
1234567
importorg.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:

Example 55. Declaring qualifier types for mapping methods
1234567
importorg.mapstruct.Qualifier;@Qualifier@Target(ElementType.METHOD)@Retention(RetentionPolicy.CLASS)public@interface EnglishToGerman {}
1234567
importorg.mapstruct.Qualifier;@Qualifier@Target(ElementType.METHOD)@Retention(RetentionPolicy.CLASS)public@interface GermanToEnglish {}

Please take note of the targetTitleTranslator on type level,EnglishToGerman,GermanToEnglish on method level!

Then, using the qualifiers, the mapping could look like this:

Example 56. Mapper using qualifiers
1234567
@Mapper( uses = Titles.class )publicinterfaceMovieMapper {@Mapping( target ="title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } )     GermanRelease toGerman( OriginalRelease movies );}
Example 57. Custom mapper qualifying the methods it provides
12345678910111213
@TitleTranslatorpublicclassTitles {@EnglishToGermanpublicString translateTitleEG(String title) {// some mapping logic    }@GermanToEnglishpublicString translateTitleGE(String title) {// some mapping logic    }}

Please make sure the used retention policy equals retention policyCLASS (@Retention(RetentionPolicy.CLASS)).

A class / method annotated with a qualifier will not qualify anymore for mappings that do not have thequalifiedBy element.

The same mechanism is also present on bean mappings:@BeanMapping#qualifiedBy: it selects the factory method marked with the indicated qualifier.

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:

Example 58. Custom mapper, annotating the methods to qualify by means of@Named
12345678910111213
@Named("TitleTranslator")publicclassTitles {@Named("EnglishToGerman")publicString translateTitleEG(String title) {// some mapping logic    }@Named("GermanToEnglish")publicString translateTitleGE(String title) {// some mapping logic    }}
Example 59. Mapper using named
1234567
@Mapper( uses = Titles.class )publicinterfaceMovieMapper {@Mapping( target ="title", qualifiedByName = {"TitleTranslator","EnglishToGerman" } )     GermanRelease toGerman( OriginalRelease movies );}

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.

5.10. Combining qualifiers with defaults

Please note that theMapping#defaultValue is in essence aString, which needs to be converted to theMapping#target. Providing aMapping#qualifiedByName orMapping#qualifiedBy will force MapStruct to use that method. If you want different behavior for theMapping#defaultValue, then please provide an appropriate mapping method. This mapping method needs to transforms aString into the desired type ofMapping#target and also be annotated so that it can be found by theMapping#qualifiedByName orMapping#qualifiedBy.

Example 60. Mapper using defaultValue
1234567891011
@MapperpublicinterfaceMovieMapper {@Mapping( target ="category", qualifiedByName ="CategoryToString", defaultValue ="DEFAULT" )     GermanRelease toGerman( OriginalRelease movies );@Named("CategoryToString")defaultString defaultValueForQualifier(Category cat) {// some mapping logic     }}

In the above example in case that category is null, the methodCategoryToString( Enum.valueOf( Category.class, "DEFAULT" ) ) will be called and the result will be set to the category field.

Example 61. Mapper using defaultValue and default method.
12345678910111213141516
@MapperpublicinterfaceMovieMapper {@Mapping( target ="category", qualifiedByName ="CategoryToString", defaultValue ="Unknown" )     GermanRelease toGerman( OriginalRelease movies );@Named("CategoryToString")defaultString defaultValueForQualifier(Category cat) {// some mapping logic     }@Named("CategoryToString")defaultString defaultValueForQualifier(String value) {return value;     }}

In the above example in case that category is null, the methoddefaultValueForQualifier( "Unknown" ) will be called and the result will be set to the category field.

If the above mentioned methods do not work there is the option to usedefaultExpression to set the default value.

Example 62. Mapper using defaultExpression
1234567891011
@MapperpublicinterfaceMovieMapper {@Mapping( target ="category", qualifiedByName ="CategoryToString", defaultExpression ="java(\"Unknown\")" )     GermanRelease toGerman( OriginalRelease movies );@Named("CategoryToString")defaultString defaultValueForQualifier(Category cat) {// some mapping logic     }}

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 theJava 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:

Example 63. Mapper with collection mapping methods
123456789
@MapperpublicinterfaceCarMapper {Set<String> integerSetToStringSet(Set<Integer> integers);List<CarDto> carsToCarDtos(List<Car> cars);    CarDto carToCarDto(Car car);}

The generated implementation of theintegerSetToStringSet performs the conversion fromInteger toString for each element, while the generatedcarsToCarDtos() method invokes thecarToCarDto() method for each contained element as shown in the following:

Example 64. Generated collection mapping methods
123456789101112131415161718192021222324252627282930
//GENERATED CODE@OverridepublicSet<String> integerSetToStringSet(Set<Integer> integers) {if ( integers ==null ) {returnnull;    }Set<String> set =newLinkedHashSet<String>();for (Integer integer : integers ) {        set.add(String.valueOf( integer ) );    }return set;}@OverridepublicList<CarDto> carsToCarDtos(List<Car> cars) {if ( cars ==null ) {returnnull;    }List<CarDto> list =newArrayList<CarDto>();for ( Car car : cars ) {        list.add( carToCarDto( car ) );    }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. fromCar#passengers (of typeList<Person>) toCarDto#passengers (of typeList<PersonDto>).

Example 65. Usage of collection mapping method to map a bean property
123
//GENERATED CODEcarDto.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 mapped elements:

Example 66. Usage of an adding method for collection mapping
123
//GENERATED CODEcarDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) );...

It is not allowed to declare mapping methods with an iterable source (from a java package) and a non-iterable target or the other way around. An error will be raised when detecting this situation.

6.1. Mapping maps

Also map-based mapping methods are supported. The following shows an example:

Example 67. Map mapping method
12345
publicinterfaceSourceTargetMapper {@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:

Example 68. Generated implementation of map mapping method
12345678910111213141516171819202122232425
//GENERATED CODE@OverridepublicMap<Long,Date> stringStringMapToLongDateMap(Map<String,String> source) {if ( source ==null ) {returnnull;    }Map<Long,Date> map =newLinkedHashMap<Long,Date>();for (Map.Entry<String,String> entry : source.entrySet() ) {Long key =Long.parseLong( entry.getKey() );Date value;try {            value =newSimpleDateFormat("dd.MM.yyyy" ).parse( entry.getValue() );        }catch(ParseException e ) {thrownewRuntimeException( e );        }        map.put( key, value );    }return map;}

6.2. Collection mapping strategies

MapStruct has aCollectionMappingStrategy, with the possible values:ACCESSOR_ONLY,SETTER_PREFERRED,ADDER_PREFERRED andTARGET_IMMUTABLE.

In the table below, the dash- indicates a property name. Next, the trailings indicates the plural form. The table explains the options and how they are applied to the presence/absence of aset-s,add- and / orget-s method on the target object:

Table 2. Collection mapping strategy options
OptionOnly target set-s AvailableOnly target add- AvailableBoth set-s / add- AvailableNo set-s / add- AvailableExisting Target(@TargetType)

ACCESSOR_ONLY

set-s

get-s

set-s

get-s

get-s

SETTER_PREFERRED

set-s

add-

set-s

get-s

get-s

ADDER_PREFERRED

set-s

add-

add-

get-s

get-s

TARGET_IMMUTABLE

set-s

exception

set-s

exception

set-s

Some background: Anadder method is typically used in case ofgenerated (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 appropriateadder, MapStruct will try to make a match between the generic parameter type of the underlying collection and the single argument of a candidateadder. When there are more candidates, the pluralsetter /getter name is converted to singular and will be used in addition to make a match.

The optionDEFAULT 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 optionDEFAULT is synonymous toACCESSOR_ONLY.

When working with anadder method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. anArrayList). You can use factories to create a new target entity with intialized collections instead of Mapstruct creating the target entity by its constructor.

6.3. Implementation types used for collection mappings

When an iterable or map mapping method declares an interface type as return type, one of its implementation types will be instantiated in the generated code. The following table shows the supported interface types and their corresponding implementation types as instantiated in the generated code:

Table 3. Collection mapping implementation types
Interface typeImplementation type

Iterable

ArrayList

Collection

ArrayList

List

ArrayList

Set

LinkedHashSet

SortedSet

TreeSet

NavigableSet

TreeSet

Map

LinkedHashMap

SortedMap

TreeMap

NavigableMap

TreeMap

ConcurrentMap

ConcurrentHashMap

ConcurrentNavigableMap

ConcurrentSkipListMap

7. Mapping Streams

The mapping ofjava.util.Stream is done in a similar way as the mapping of collection types, i.e. by defining mappingmethods with the required source and target types in a mapper interface.

The generated code will contain the creation of aStream from the providedIterable/array or will collect theprovidedStream into anIterable/array. If a mapping method or an implicit conversion for the source and targetelement types exists, then this conversion will be done inStream#map(). The following shows an example:

Example 69. Mapper with stream mapping methods
123456789
@MapperpublicinterfaceCarMapper {Set<String> integerStreamToStringSet(Stream<Integer> integers);List<CarDto> carsToCarDtos(Stream<Car> cars);    CarDto carToCarDto(Car car);}

The generated implementation of theintegerStreamToStringSet() performs the conversion fromInteger toString foreach element, while the generatedcarsToCarDtos() method invokes thecarToCarDto() method for each containedelement as shown in the following:

Example 70. Generated stream mapping methods
1234567891011121314151617181920
//GENERATED CODE@OverridepublicSet<String> integerStreamToStringSet(Stream<Integer> integers) {if ( integers ==null ) {returnnull;    }return integers.map( integer ->String.valueOf( integer ) )        .collect( Collectors.toCollection(LinkedHashSet<String>::new ) );}@OverridepublicList<CarDto> carsToCarDtos(Stream<Car> cars) {if ( cars ==null ) {returnnull;    }return cars.map( car -> carToCarDto( car ) )        .collect( Collectors.toCollection(ArrayList<CarDto>::new ) );}

If a mapping from aStream to anIterable or an array is performed, then the passedStream will be consumedand it will no longer be possible to consume it.

The same implementation types as inImplementation types used for collection mappings are used for the creation of thecollection when doingStream toIterable mapping.

8. Mapping Values

8.1. Mapping enum to 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.

The following shows an example:

Example 71. Enum mapping method
123456789101112
@MapperpublicinterfaceOrderMapper {    OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );@ValueMappings({@ValueMapping(target ="SPECIAL", source ="EXTRA"),@ValueMapping(target ="DEFAULT", source ="STANDARD"),@ValueMapping(target ="DEFAULT", source ="NORMAL")    })    ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);}
Example 72. Enum mapping method result
12345678910111213141516171819202122232425262728
// GENERATED CODEpublicclassOrderMapperImplimplements OrderMapper {@Overridepublic ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {if ( orderType ==null ) {returnnull;        }        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:thrownewIllegalArgumentException("Unexpected enum constant:" + orderType );        }return externalOrderType_;    }}

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 generatedmapping method will throw anIllegalStateException 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 and only applies to the source. It comes in two flavors:<ANY_REMAINING> and<ANY_UNMAPPED>. They cannot be used at the same time.

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 willnot 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 handlenull sources andnull targets by means of the<NULL> keyword.

In addition, the constant value<THROW_EXCEPTION> can be used for throwing an exception for particular value mappings. This value is only applicable toValueMapping#target() and notValueMapping#source() since MapStruct can’t map from exceptions.

Constants for<ANY_REMAINING>,<ANY_UNMAPPED>,<THROW_EXCEPTION> and<NULL> are available in theMappingConstants class.

Finally@InheritInverseConfiguration and@InheritConfiguration can be used in combination with@ValueMappings.<ANY_REMAINING> and<ANY_UNMAPPED> will be ignored in that case.

The following code snippets exemplify the use of the aforementioned constants.

Example 73. Enum mapping method,<NULL> and<ANY_REMAINING>
123456789101112
@MapperpublicinterfaceSpecialOrderMapper {    SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );@ValueMappings({@ValueMapping( source = MappingConstants.NULL, target ="DEFAULT" ),@ValueMapping( source ="STANDARD", target = MappingConstants.NULL ),@ValueMapping( source = MappingConstants.ANY_REMAINING, target ="SPECIAL" )    })    ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);}
Example 74. Enum mapping method result,<NULL> and<ANY_REMAINING>
123456789101112131415161718192021222324
// GENERATED CODEpublicclassSpecialOrderMapperImplimplements SpecialOrderMapper {@Overridepublic 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 theRETAIL andB2B when<ANY_UNMAPPED> was used instead of<ANY_REMAINING>.

Example 75. Enum mapping method with<THROW_EXCEPTION>
1234567891011
@MapperpublicinterfaceSpecialOrderMapper {    SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );@ValueMappings({@ValueMapping( source ="STANDARD", target ="DEFAULT" ),@ValueMapping( source ="C2C", target = MappingConstants.THROW_EXCEPTION )    })    ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);}
Example 76. Enum mapping method with<THROW_EXCEPTION> result
123456789101112131415161718192021
// GENERATED CODEpublicclassSpecialOrderMapperImplimplements SpecialOrderMapper {@Overridepublic ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {if ( orderType ==null ) {returnnull;        }        ExternalOrderType externalOrderType;switch ( orderType ) {case STANDARD: externalOrderType = ExternalOrderType.DEFAULT;break;case C2C:thrownewIllegalArgumentException("Unexpected enum constant:" + orderType );default:thrownewIllegalArgumentException("Unexpected enum constant:" + orderType );        }return externalOrderType;    }}

8.2. Mapping enum-to-String or String-to-enum

MapStruct supports enum to a String mapping along the same lines as is described inenum-to-enum types. There are similarities and differences:

enum toString

  1. Similarity: All not explicit defined mappings will result in each source enum constant value being mapped aString value with the same constant value.

  2. Similarity:<ANY_UNMAPPED> stops after handling defined mapping and proceeds to the switch/default clause value.

  3. Difference:<ANY_REMAINING> will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type.

  4. Difference: Given 1. and 3. there will never be unmapped values.

  5. Similarity:<THROW_EXCEPTION> can be used for throwing an exception for particular enum values.

String to enum

  1. Similarity: All not explicit defined mappings will result in the target enum constant mapped from theString value when that matches the target enum constant name.

  2. Similarity:<ANY_UNMAPPED> stops after handling defined mapping and proceeds to the switch/default clause value.

  3. Similarity:<ANY_REMAINING> will create a mapping for each target enum constant and proceed to the switch/default clause value.

  4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values,String has unlimited options). Failing to specify<ANY_REMAINING> or<ANY_UNMAPPED> will result in a warning.

  5. Similarity:<THROW_EXCEPTION> can be used for throwing an exception for any arbitraryString value.

8.3. Custom name transformation

When no@ValueMapping(s) are defined then each constant from the source enum is mapped to a constant with the same name in the target enum type.However, there are cases where the source enum needs to be transformed before doing the mapping.E.g. a suffix needs to be applied to map from the source into the target enum.

Example 77. Enum types
1234567891011
publicenum CheeseType {    BRIE,    ROQUEFORT}publicenum CheeseTypeSuffixed {    BRIE_TYPE,    ROQUEFORT_TYPE}
Example 78. Enum mapping method with custom name transformation strategy
1234567891011
@MapperpublicinterfaceCheeseMapper {    CheeseMapper INSTANCE = Mappers.getMapper( CheeseMapper.class );@EnumMapping(nameTransformationStrategy ="suffix", configuration ="_TYPE")    CheeseTypeSuffixed map(CheeseType cheese);@InheritInverseConfiguration    CheeseType map(CheeseTypeSuffix cheese);}
Example 79. Enum mapping method with custom name transformation strategy result
1234567891011121314151617181920212223242526272829303132333435363738394041
// GENERATED CODEpublicclassCheeseSuffixMapperImplimplements CheeseSuffixMapper {@Overridepublic CheeseTypeSuffixed map(CheeseType cheese) {if ( cheese ==null ) {returnnull;        }        CheeseTypeSuffixed cheeseTypeSuffixed;switch ( cheese ) {case BRIE: cheeseTypeSuffixed = CheeseTypeSuffixed.BRIE_TYPE;break;case ROQUEFORT: cheeseTypeSuffixed = CheeseTypeSuffixed.ROQUEFORT_TYPE;break;default:thrownewIllegalArgumentException("Unexpected enum constant:" + cheese );        }return cheeseTypeSuffixed;    }@Overridepublic CheeseType map(CheeseTypeSuffixed cheese) {if ( cheese ==null ) {returnnull;        }        CheeseType cheeseType;switch ( cheese ) {case BRIE_TYPE: cheeseType = CheeseType.BRIE;break;case ROQUEFORT_TYPE: cheeseType = CheeseType.ROQUEFORT;break;default:thrownewIllegalArgumentException("Unexpected enum constant:" + cheese );        }return cheeseType;    }}

MapStruct provides the following out of the box enum name transformation strategies:

  • suffix - Applies a suffix on the source enum

  • stripSuffix - Strips a suffix from the source enum

  • prefix - Applies a prefix on the source enum

  • stripPrefix - Strips a prefix from the source enum

  • case - Applies case transformation to the source enum. Supportedcase transformations are:

    • upper - Performs upper case transformation to the source enum

    • lower - Performs lower case transformation to the source enum

    • capital - Performs capitalisation of the first character of every word in the source enum and everything else to lowercase. A word is split by "_"

It is also possible to register custom strategies.For more information on how to do that have a look atCustom Enum Transformation Strategy

8.4. ValueMapping Composition

The@ValueMapping annotation supports now@Target withElementType#ANNOTATION_TYPE in addition toElementType#METHOD.This allows@ValueMapping to be used on other (user defined) annotations for re-use purposes.For example:

Example 80. Custom value mapping annotations
12345
@Retention(RetentionPolicy.CLASS )@ValueMapping(source ="EXTRA", target ="SPECIAL")@ValueMapping(source = MappingConstants.ANY_REMAINING, target ="DEFAULT")public@interface CustomValueAnnotation {}

It can be used to describe some common value mapping relationships to avoid duplicate declarations, as in the following example:

Example 81. Using custom combination annotations
12345678910
@MapperpublicinterfaceValueMappingCompositionMapper {@CustomValueAnnotation    ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);@CustomValueAnnotation@ValueMapping(source ="STANDARD", target ="SPECIAL")    ExternalOrderType duplicateAnnotation(OrderType orderType);}

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 createsObjectFactory classes for obtaining new instances of schema types.

To make use of custom factories register them via@Mapper#uses() as described inInvoking 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 type and invoke this method instead of calling the default constructor:

Example 82. Custom object factories
123456
publicclassDtoFactory {public CarDto createCarDto() {return// ... custom factory logic     }}
123456
publicclassEntityFactory {public <Textends BaseEntity> T createEntity(@TargetTypeClass<T> entityClass) {return// ... custom factory logic     }}
123456789
@Mapper(uses= { DtoFactory.class, EntityFactory.class } )publicinterfaceCarMapper {    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );    CarDto carToCarDto(Car car);    Car carDtoToCar(CarDto carDto);}
123456789101112131415161718192021222324252627282930313233
//GENERATED CODEpublicclassCarMapperImplimplements CarMapper {privatefinal DtoFactory dtoFactory =new DtoFactory();privatefinal EntityFactory entityFactory =new EntityFactory();@Overridepublic CarDto carToCarDto(Car car) {if ( car ==null ) {returnnull;        }        CarDto carDto = dtoFactory.createCarDto();//map properties...return carDto;    }@Overridepublic Car carDtoToCar(CarDto carDto) {if ( carDto ==null ) {returnnull;        }        Car car = entityFactory.createEntity( Car.class );//map properties...return car;    }}
Example 83. Custom object factories with update methods
123456789
@Mapper(uses = { DtoFactory.class, EntityFactory.class, CarMapper.class } )publicinterfaceOwnerMapper {    OwnerMapper INSTANCE = Mappers.getMapper( OwnerMapper.class );void updateOwnerDto(Owner owner,@MappingTarget OwnerDto ownerDto);void updateOwner(OwnerDto ownerDto,@MappingTargetOwner owner);}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
//GENERATED CODEpublicclassOwnerMapperImplimplements OwnerMapper {privatefinal DtoFactory dtoFactory =new DtoFactory();privatefinal EntityFactory entityFactory =new EntityFactory();privatefinal OwnerMapper ownerMapper = Mappers.getMapper( OwnerMapper.class );@Overridepublicvoid 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 );        }// updating other properties    }@Overridepublicvoid updateOwner(OwnerDto ownerDto,@MappingTargetOwner 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 );        }// updating other properties    }}

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@ObjectFactoryannotation is necessary to let MapStruct know that the given method is only a factory method.

Example 84. Custom object factories with@ObjectFactory
1234567
publicclassDtoFactory {@ObjectFactorypublic CarDto createCarDto(Car car) {return// ... custom factory logic     }}

10. Advanced mapping options

This chapter describes several advanced options which allow to fine-tune the behavior of the generated mapping code as needed.

10.1. Default values and constants

Default values can be specified to set a predefined value to a target property if the corresponding source property isnull. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values. When the target type is a primitive or a boxed type, the String value is taken literal. Bit / octal / decimal / hex patterns are allowed in such a case as long as they are a valid literal.In all other cases, constant or default values are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property.

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:

Example 85. Mapping method with default values and constants
1234567891011121314
@Mapper(uses = StringListMapper.class)publicinterfaceSourceTargetMapper {    SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );@Mapping(target ="stringProperty", source ="stringProp", defaultValue ="undefined")@Mapping(target ="longProperty", source ="longProp", defaultValue ="-1")@Mapping(target ="stringConstant", constant ="Constant Value")@Mapping(target ="integerConstant", constant ="14")@Mapping(target ="longWrapperConstant", constant ="3001")@Mapping(target ="dateConstant", dateFormat ="dd-MM-yyyy", constant ="09-01-2014")@Mapping(target ="stringListConstants", constant ="jack-jill-tom")Target sourceToTarget(Source s);}

Ifs.getStringProp() == null, then the target propertystringProperty will be set to"undefined" instead of applying the value froms.getStringProp(). Ifs.getLongProperty() == null, then the target propertylongProperty will be set to-1.The String"Constant Value" is set as is to the target propertystringConstant. The value"3001" is type-converted to theLong (wrapper) class of target propertylongWrapperConstant. Date properties also require a date format. The constant"jack-jill-tom" demonstrates how the hand-written classStringListMapper is invoked to map the dash-separated list into aList<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:

Example 86. Mapping method using an expression
123456789
@MapperpublicinterfaceSourceTargetMapper {    SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );@Mapping(target ="timeAndFormat",         expression ="java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")Target sourceToTarget(Source s);}

The example demonstrates how the source propertiestime andformat are composed into one target propertyTimeAndFormat. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of theTimeAndFormat class (unless it’s used otherwise explicitly in theSourceTargetMapper). This can be resolved by definingimports on the@Mapper annotation.

Example 87. Declaring an import
1234567891011
imports org.sample.TimeAndFormat;@Mapper( imports = TimeAndFormat.class )publicinterfaceSourceTargetMapper {    SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );@Mapping(target ="timeAndFormat",         expression ="java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")Target sourceToTarget(Source s);}

10.3. Default Expressions

Default expressions are a combination of default values and expressions. They will only be used when the source attribute isnull.

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 a default expression can be used to set a value when the source attribute is not present (e.g. isnull):

Example 88. Mapping method using a default expression
12345678910
imports java.util.UUID;@Mapper( imports =UUID.class )publicinterfaceSourceTargetMapper {    SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );@Mapping(target="id", source="sourceId", defaultExpression ="java( UUID.randomUUID().toString() )")Target sourceToTarget(Source s);}

The example demonstrates how to use defaultExpression to set anID field if the source field is null, this could be used to take the existingsourceId from the source object if it is set, or create a newId if it isn’t. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of theUUID class (unless it’s used otherwise explicitly in theSourceTargetMapper). This can be resolved by defining imports on the @Mapper annotation (seeExpressions).

10.4. Subclass Mapping

When both input and result types have an inheritance relation, you would want the correct specialization be mapped to the matching specialization.Suppose anApple and aBanana, which are both specializations ofFruit.

Example 89. Specifying the sub class mappings of a fruit mapping
12345678
@MapperpublicinterfaceFruitMapper {@SubclassMapping( source = AppleDto.class, target = Apple.class )@SubclassMapping( source = BananaDto.class, target = Banana.class )    Fruit map( FruitDto source );}

If you would just use a normal mapping both theAppleDto and theBananaDto would be made into aFruit object, instead of anApple and aBanana object.By using the subclass mapping anAppleDtoToApple mapping will be used forAppleDto objects, and anBananaDtoToBanana mapping will be used forBananaDto objects.If you try to map aGrapeDto it would still turn it into aFruit.

In the case that theFruit is an abstract class or an interface, you would get a compile error.

To allow mappings for abstract classes or interfaces you need to set thesubclassExhaustiveStrategy toRUNTIME_EXCEPTION, you can do this at the@MapperConfig,@Mapper or@BeanMapping annotations. If you then pass aGrapeDto anIllegalArgumentException will be thrown because it is unknown how to map aGrapeDto.Adding the missing (@SubclassMapping) for it will fix that.

Mapping method selection based on qualifiers can be used to further control which methods may be chosen to map a specific subclass. For that, you will need to use one ofSubclassMapping#qualifiedByName orSubclassMapping#qualifiedBy.

If the mapping method for the subclasses does not exist it will be created and any other annotations on the fruit mapping method will be inherited by the newly generated mappings.

Combining@SubclassMapping with update methods is not supported.If you try to use subclass mappings there will be a compile error.The same issue exists for the@Context and@TargetType parameters.

10.5. Determining the result type

When result types have an inheritance relation, selecting either mapping method (@Mapping) or a factory method (@BeanMapping) can become ambiguous. Suppose an Apple and a Banana, which are both specializations of Fruit.

Example 90. Specifying the result type of a bean mapping method
1234567
@Mapper( uses = FruitFactory.class )publicinterfaceFruitMapper {@BeanMapping( resultType = Apple.class )    Fruit map( FruitDto source );}
12345678910
publicclassFruitFactory {public Apple createApple() {returnnew Apple("Apple" );    }public Banana createBanana() {returnnew Banana("Banana" );    }}

So, whichFruit must be factorized in the mapping methodFruit map(FruitDto source);? ABanana or anApple? Here’s where 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.

The same mechanism is present on mapping:@Mapping#resultType and works like you expect it would: it selects the mapping method with the desired result type when present.

The mechanism is also present on iterable mapping and map mapping.@IterableMapping#elementTargetType is used to select the mapping method with the desired element in the resultingIterable. For the@MapMapping a similar purpose is served by means of#MapMapping#keyTargetType andMapMapping#valueTargetType.

10.6. Controlling mapping result for 'null' arguments

MapStruct offers control over the object to create when the source argument of the mapping method equalsnull. By defaultnull will be returned.

However, by specifyingnullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT on@BeanMapping,@IterableMapping,@MapMapping, or globally on@Mapper or@MapperConfig, the mapping result can be altered to return emptydefault values. This means for:

  • Bean mappings: an 'empty' target bean will be returned, with the exception of constants and expressions, they will be populated when present.

  • Iterables / Arrays: an empty iterable will be returned.

  • Maps: an empty map will be returned.

The strategy works in a hierarchical fashion. SettingnullValueMappingStrategy on mapping method level will override@Mapper#nullValueMappingStrategy, and@Mapper#nullValueMappingStrategy will override@MapperConfig#nullValueMappingStrategy.

10.7. Controlling mapping result for 'null' collection or map arguments

WithControlling mapping result for 'null' arguments it is possible to control how the return type should be constructed when the source argument of the mapping method isnull.That is applied for all mapping methods (bean, iterable or map mapping methods).

However, MapStruct also offers a more dedicated way to control how collections / maps should be mapped.e.g. return default (empty) collections / maps, but returnnull for beans.

For collections (iterables) this can be controlled through:

  • MapperConfig#nullValueIterableMappingStrategy

  • Mapper#nullValueIterableMappingStrategy

  • IterableMapping#nullValueMappingStrategy

For maps this can be controlled through:

  • MapperConfig#nullValueMapMappingStrategy

  • Mapper#nullValueMapMappingStrategy

  • MapMapping#nullValueMappingStrategy

How the value of theNullValueMappingStrategy is applied is the same as inControlling mapping result for 'null' arguments

10.8. Controlling mapping result for 'null' properties in bean mappings (update mapping methods only).

MapStruct offers control over the property to set in an@MappingTarget annotated target bean when the source property equalsnull or the presence check method results in 'absent'.

By default the target property will be set to null.

However:

  1. By specifyingnullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT on@Mapping,@BeanMapping,@Mapper or@MapperConfig, the mapping result can be altered to returndefault values.ForList MapStruct generates anArrayList, forMap aLinkedHashMap, for arrays an empty array, forString"" and for primitive / boxed types a representation offalse or0.For all other objects an new instance is created. Please note that a default constructor is required. If not available, use the@Mapping#defaultValue.

  2. By specifyingnullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE on@Mapping,@BeanMapping,@Mapper or@MapperConfig, the mapping result will be equal to the original value of the@MappingTarget annotated target.

The strategy works in a hierarchical fashion. SettingnullValuePropertyMappingStrategy on mapping method level will override@Mapper#nullValuePropertyMappingStrategy, and@Mapper#nullValuePropertyMappingStrategy will override@MapperConfig#nullValuePropertyMappingStrategy.

Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor (seeCollectionMappingStrategy), MapStruct will always generate a source propertynull check, regardless of the value of theNullValuePropertyMappingStrategy, to avoid addition ofnull to the target collection or map. Since the target is assumed to be initialised this strategy will not be applied.

NullValuePropertyMappingStrategy also applies when the presence checker returnsnot present.

10.9. Controlling checking result for 'null' properties in bean mapping

MapStruct offers control over when to generate anull check. By default (nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION) anull check will be generated for:

  • direct setting of source value to target value when target is primitive and source is not.

  • applying type conversion and then:

    1. calling the setter on the target.

    2. calling another type conversion and subsequently calling the setter on the target.

    3. 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. Therefore 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' tonull. For instance: mappingnull to a default value.

The optionnullValueCheckStrategy = NullValueCheckStrategy.ALWAYS will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean.

The strategy works in a hierarchical fashion.@Mapping#nullValueCheckStrategy will override@BeanMapping#nullValueCheckStrategy,@BeanMapping#nullValueCheckStrategy will override@Mapper#nullValueCheckStrategy and@Mapper#nullValueCheckStrategy will override@MapperConfig#nullValueCheckStrategy.

10.10. Source presence checking

Some frameworks generate bean properties that have a source presence checker. Often this is in the form of a methodhasXYZ,XYZ being a property on the source bean in a bean mapping method. MapStruct will call thishasXYZ instead of performing anull check when it finds suchhasXYZ method.

The source presence checker name can be changed in the MapStruct service provider interface (SPI). It can also be deactivated in this way.

Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor (seeCollectionMappingStrategy), MapStruct will always generate a source propertynull check, regardless the value of theNullValueCheckStrategy to avoid addition ofnull to the target collection or map.

10.11. Conditional Mapping

Conditional Mapping is a type ofSource presence checking.The difference is that it allows users to write custom condition methods that will be invoked to check if a property needs to be mapped or not.Conditional mapping can also be used to check if a source parameter should be mapped or not.

A custom condition method for properties is a method that is annotated withorg.mapstruct.Condition and returnsboolean.A custom condition method for source parameters is annotated withorg.mapstruct.SourceParameterCondition,org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS) or meta-annotated withCondition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)

e.g. if you only want to map a String property when it is notnull, and it is not empty then you can do something like:

Example 91. Mapper using custom condition check method
12345678910
@MapperpublicinterfaceCarMapper {    CarDto carToCarDto(Car car);@Conditiondefaultboolean isNotEmpty(String value) {return value !=null && !value.isEmpty();    }}

The generated mapper will look like:

Example 92. Custom condition check in generated implementation
1234567891011121314151617181920
// GENERATED CODEpublicclassCarMapperImplimplements CarMapper {@Overridepublic CarDto carToCarDto(Car car) {if ( car ==null ) {returnnull;        }        CarDto carDto =new CarDto();if ( isNotEmpty( car.getOwner() ) ) {            carDto.setOwner( car.getOwner() );        }// Mapping of other propertiesreturn carDto;    }}

When using this in combination with an update mapping method it will replace thenull-check there, for example:

Example 93. Update mapper using custom condition check method
12345678910
@MapperpublicinterfaceCarMapper {    CarDto carToCarDto(Car car,@MappingTarget CarDto carDto);@Conditiondefaultboolean isNotEmpty(String value) {return value !=null && !value.isEmpty();    }}

The generated update mapper will look like:

Example 94. Custom condition check in generated implementation
1234567891011121314151617181920
// GENERATED CODEpublicclassCarMapperImplimplements CarMapper {@Overridepublic CarDto carToCarDto(Car car, CarDto carDto) {if ( car ==null ) {return carDto;        }if ( isNotEmpty( car.getOwner() ) ) {            carDto.setOwner( car.getOwner() );        }else {            carDto.setOwner(null );        }// Mapping of other propertiesreturn carDto;    }}

Additionally@TargetPropertyName or@SourcePropertyName of typejava.lang.String can be used in custom condition check method:

Example 95. Mapper using custom condition check method with@TargetPropertyName and@SourcePropertyName
1234567891011121314151617181920212223
@MapperpublicinterfaceCarMapper {@Mapping(target ="owner", source ="ownerName")    CarDto carToCarDto(Car car,@MappingTarget CarDto carDto);@Conditiondefaultboolean isNotEmpty(String value,@TargetPropertyNameString targetPropertyName,@SourcePropertyNameString sourcePropertyName    ) {if ( targetPropertyName.equals("owner" )             && sourcePropertyName.equals("ownerName" ) ) {return value !=null                    && !value.isEmpty()                    && !value.equals( value.toLowerCase() );        }return value !=null && !value.isEmpty();    }}

The generated mapper with@TargetPropertyName and@SourcePropertyName will look like:

Example 96. Custom condition check in generated implementation
1234567891011121314151617181920
// GENERATED CODEpublicclassCarMapperImplimplements CarMapper {@Overridepublic CarDto carToCarDto(Car car, CarDto carDto) {if ( car ==null ) {return carDto;        }if ( isNotEmpty( car.getOwner(),"owner","ownerName" ) ) {            carDto.setOwner( car.getOwner() );        }else {            carDto.setOwner(null );        }// Mapping of other propertiesreturn carDto;    }}

If there is a custom@Condition method applicable for the property it will have a precedence over a presence check method in the bean itself.

Methods annotated with@Condition in addition to the value of the source property can also have the source parameter as an input.

@TargetPropertyName and@SourcePropertyName parameters can only be used in@Condition methods.

Mapping method selection based on qualifiers is also valid for@Condition methods.In order to use a more specific condition method you will need to use one ofMapping#conditionQualifiedByName orMapping#conditionQualifiedBy.

If we want to only map cars that have an id provided then we can do something like:

Example 97. Mapper using custom condition source parameter check method
12345678910
@MapperpublicinterfaceCarMapper {    CarDto carToCarDto(Car car);@SourceParameterConditiondefaultboolean hasCar(Car car) {return car !=null && car.getId() !=null;    }}

The generated mapper will look like:

Example 98. Custom condition source parameter check generated implementation
123456789101112131415161718
// GENERATED CODEpublicclassCarMapperImplimplements CarMapper {@Overridepublic CarDto carToCarDto(Car car) {if ( !hasCar( car ) ) {returnnull;        }        CarDto carDto =new CarDto();        carDto.setOwner( car.getOwner() );// Mapping of other propertiesreturn carDto;    }}

10.12. 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 throws clause can be defined in the mapping method:

Example 99. Mapper using custom method declaring checked exception
12345
@Mapper(uses = HandWritten.class)publicinterfaceCarMapper {    CarDto carToCarDto(Car car)throws GearException;}

The hand written logic might look like this:

Example 100. Custom mapping method declaring checked exception
123456789101112131415
publicclassHandWritten {privatestaticfinalString[] GEAR = {"ONE","TWO","THREE","OVERDRIVE","REVERSE"};publicString toGear(Integer gear)throws GearException, FatalException {if ( gear ==null ) {thrownew FatalException("null is not a valid gear");        }if ( gear <0 && gear > GEAR.length ) {thrownew GearException("invalid gear");        }return GEAR[gear];    }}

MapStruct now, wraps theFatalException in atry-catch block and rethrows an uncheckedRuntimeException. MapStruct delegates handling of theGearException to the application logic because it is defined as throws clause in thecarToCarDto method:

Example 101. try-catch block in generated implementation
1234567891011121314151617
// GENERATED CODE@Overridepublic CarDto carToCarDto(Car car)throws GearException {if ( car ==null ) {returnnull;    }    CarDto carDto =new CarDto();try {        carDto.setGear( handWritten.toGear( car.getGear() ) );    }catch ( FatalException e ) {thrownewRuntimeException( e );    }return carDto;}

Somenotes 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.

11. Reusing mapping configurations

This chapter discusses different means of reusing mapping configurations for several mapping methods: "inheritance" of configuration from other methods and sharing central configuration between multiple mapper types.

11.1. Mapping configuration inheritance

Method-level configuration annotations such as@Mapping,@BeanMapping,@IterableMapping, etc., can beinherited from one mapping method to asimilar method using the annotation@InheritConfiguration:

Example 102. Update method inheriting its configuration
123456789
@MapperpublicinterfaceCarMapper {@Mapping(target ="numberOfSeats", source ="seatCount")    Car carDtoToCar(CarDto car);@InheritConfigurationvoid carDtoIntoCar(CarDto carDto,@MappingTarget Car car);}

The example above declares a mapping methodcarDtoToCar() with a configuration to define how the propertynumberOfSeats in the typeCar shall be mapped. The update method that performs the mapping on an existing instance ofCar 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 methodA can inherit the configuration from another methodB if all types ofA (source types and result type) are assignable to the corresponding types ofB.

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 inShared 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.

@InheritConfiguration cannot refer to methods in a used mapper.

11.2. Inverse mappings

In case of bi-directional mappings, e.g. from entity to DTO and from DTO to entity, the mapping rules for the forward method and the reverse method are often similar and can simply be inversed by switchingsource andtarget.

Use the annotation@InheritInverseConfiguration to indicate that a method shall inherit the inverse configuration of the corresponding reverse method.

In the example below, there is no need to write the inverse mapping manually. Think of a case where there are several mappings, so writing the inverse ones can be cumbersome and error prone.

Example 103. Inverse mapping method inheriting its configuration and ignoring some of them
12345678910
@MapperpublicinterfaceCarMapper {@Mapping(target ="seatCount", source ="numberOfSeats")    CarDto carToDto(Car car);@InheritInverseConfiguration@Mapping(target ="numberOfSeats", ignore =true)    Car carDtoToCar(CarDto carDto);}

Here thecarDtoToCar() method is the reverse mapping method forcarToDto(). Note that any attribute mappings fromcarToDto() 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 byignore,expression orconstant in the mapping, e.g. like this:@Mapping(target = "numberOfSeats", ignore=true).

A methodA is considered areverse method of a methodB, if the result type ofA is thesame as the single source type ofB and if the single source type ofA is thesame as the result type ofB.

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 thename property like this:@InheritInverseConfiguration(name = "carToDto").

@InheritConfiguration takes, in case of conflict precedence over@InheritInverseConfiguration.

Configurations are inherited transitively. So if methodC defines a mapping@Mapping( target = "x", ignore = true),B defines a mapping@Mapping( target = "y", ignore = true), then ifA inherits fromB inherits fromC,A will inherit mappings for both propertyx andy.

@Mapping#expression,@Mapping#defaultExpression,@Mapping#defaultValue and@Mapping#constant are excluded (silently ignored) in@InheritInverseConfiguration.

@Mapping#ignore is only applied when@Mapping#source is also present in@InheritInverseConfiguration.

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.

@InheritInverseConfiguration cannot refer to methods in a used mapper.

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 asuses are simply combined:

Example 104. Mapper configuration class and mapper using it
123456
@MapperConfig(    uses = CustomMapperViaMapperConfig.class,    unmappedTargetPolicy = ReportingPolicy.ERROR)publicinterfaceCentralConfig {}
123456789
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )// Effective configuration:// @Mapper(//     uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },//     unmappedTargetPolicy = ReportingPolicy.ERROR// )publicinterfaceSourceTargetMapper {  ...}

The interface holding the@MapperConfig annotation may also declareprototypes 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.

Example 105. Mapper configuration class with prototype methods
1234567891011
@MapperConfig(    uses = CustomMapperViaMapperConfig.class,    unmappedTargetPolicy = ReportingPolicy.ERROR,    mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG)publicinterfaceCentralConfig {// Not intended to be generated, but to carry inheritable mapping annotations:@Mapping(target ="primaryKey", source ="technicalKey")    BaseEntity anyDtoToEntity(BaseDto dto);}
12345678
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )publicinterfaceSourceTargetMapper {@Mapping(target ="numberOfSeats", source ="seatCount")// additionally inherited from CentralConfig, because Car extends BaseEntity and CarDto extends BaseDto:// @Mapping(target = "primaryKey", source = "technicalKey")    Car toCar(CarDto car)}

The attributes@Mapper#mappingInheritanceStrategy() /@MapperConfig#mappingInheritanceStrategy() configure when the method-level mapping configuration annotations are inherited from prototype methods in the interface to methods in the mapper:

  • 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 inMapping configuration inheritance.

  • AUTO_INHERIT_FROM_CONFIG: the configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using@InheritConfiguration(name = …​) which will causeAUTO_INHERIT_FROM_CONFIG to be ignored.

  • AUTO_INHERIT_REVERSE_FROM_CONFIG: the inverse configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using@InheritInverseConfiguration(name = …​) which will cause`AUTO_INHERIT_REVERSE_FROM_CONFIG to be ignored.

  • AUTO_INHERIT_ALL_FROM_CONFIG: both the configuration and the inverse configuration will be inherited automatically. The same rules apply as forAUTO_INHERIT_FROM_CONFIG orAUTO_INHERIT_REVERSE_FROM_CONFIG.

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.

12.1. Mapping customization with decorators

In certain cases it may be required to customize a generated mapping method, e.g. to set an additional property in the target object which can’t be set by a generated method implementation. MapStruct supports this requirement using decorators.

When working with the component modelcdi, useCDI decorators with MapStruct mappers instead of the@DecoratedWith annotation described here.

To apply a decorator to a mapper class, specify it using the@DecoratedWith annotation.

Example 106. Applying a decorator
12345678910
@Mapper@DecoratedWith(PersonMapperDecorator.class)publicinterfacePersonMapper {    PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );    PersonDto personToPersonDto(Person person);    AddressDto addressToAddressDto(Address address);}

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.

ThePersonMapperDecorator shown below customizes thepersonToPersonDto(). It sets an additional attribute which is not present in the source type of the mapping. TheaddressToAddressDto() method is not customized.

Example 107. Implementing a decorator
123456789101112131415
publicabstractclassPersonMapperDecoratorimplements PersonMapper {privatefinal PersonMapper delegate;public PersonMapperDecorator(PersonMapper delegate) {this.delegate = delegate;    }@Overridepublic 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 withcomponentModel = "default", define a constructor with a single parameter which accepts the type of the decorated mapper.

When working with the component modelsspring orjsr330, this needs to be handled differently.

12.1.1. Decorators with the Spring component model

When using@DecoratedWith on a mapper with component modelspring, 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:

Example 108. Spring-based decorator
1234567891011121314
publicabstractclassPersonMapperDecoratorimplements PersonMapper {@Autowired@Qualifier("delegate")private PersonMapper delegate;@Overridepublic 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:

Example 109. Using a decorated mapper
12
@Autowiredprivate PersonMapper personMapper;// injects the decorator, with the injected original mapper

12.1.2. Decorators with the JSR 330 component model

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):

Example 110. JSR 330 based decorator
1234567891011121314
publicabstractclassPersonMapperDecoratorimplements PersonMapper {@Inject@Named("org.examples.PersonMapperImpl_")private PersonMapper delegate;@Overridepublic 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:

Example 111. Using a decorated mapper with JSR 330
123
@Inject@Namedprivate PersonMapper personMapper;// injects the decorator, with the injected original mapper

12.2. Mapping customization with before-mapping and after-mapping methods

Decorators may not always fit the needs when it comes to customizing mappers. For example, if you need to perform the customization not only for a few selected methods, but for all methods that map specific super-types: in that case, you can usecallback methods that are invoked before the mapping starts or after the mapping finished.

Callback methods can be implemented in the abstract mapper itself, in a type reference inMapper#uses, or in a type used as@Context parameter.

Example 112. Mapper with @BeforeMapping and @AfterMapping hooks
1234567891011121314151617181920212223242526272829303132333435
@MapperpublicabstractclassVehicleMapper {@BeforeMappingprotectedvoid 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    }@AfterMappingprotectedvoid fillTank(AbstractVehicle vehicle,@MappingTarget AbstractVehicleDto result) {        result.fuelUp(new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) );    }publicabstract CarDto toCarDto(Car car);}// Generates something like this:publicclassVehicleMapperImplextends VehicleMapper {public CarDto toCarDto(Car car) {        flushEntity( car );if ( car ==null ) {returnnull;        }        CarDto carDto =new CarDto();// attributes mapping ...        fillTank( car, carDto );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 beassigned by the source or target parameters of the mapping method:

  • A parameter annotated with@MappingTarget is populated with the target instance of the mapping.

  • 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.

  • Any other parameter is populated with a source parameter of the mapping.

For non-void methods, the return value of the method invocation is returned as the result of the mapping method if it is notnull.

As with mapping methods, it is possible to specify type parameters for before/after-mapping methods.

Example 113. Mapper with @AfterMapping hook that returns a non-null value
123456789101112131415161718192021222324252627282930313233
@MapperpublicabstractclassVehicleMapper {@PersistenceContextprivate EntityManager entityManager;@AfterMappingprotected <T> T attachEntity(@MappingTarget T entity) {return entityManager.merge(entity);    }publicabstract CarDto toCarDto(Car car);}// Generates something like this:publicclassVehicleMapperImplextends VehicleMapper {public CarDto toCarDto(Car car) {if ( car ==null ) {returnnull;        }        CarDto carDto =new CarDto();// attributes mapping ...        CarDto target = attachEntity( carDto );if ( target !=null ) {return target;        }return carDto;    }}

All before/after-mapping methods thatcan be applied to a mapping methodwill be used.Mapping 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 inBeanMapping#qualifiedBy orIterableMapping#qualifiedBy.

The order of the method invocation is determined primarily by their variant:

  1. @BeforeMapping methods without parameters, a@MappingTarget parameter or a@TargetType parameter are called before any null-checks on source parameters and constructing a new target bean.

  2. @BeforeMapping methods with a@MappingTarget parameter are called after constructing a new target bean.

  3. @AfterMapping methods are called at the end of the mapping method before the lastreturn statement.

Within those groups, the method invocations are ordered by their location of definition:

  1. Methods declared on@Context parameters, ordered by the parameter order.

  2. Methods implemented in the mapper itself.

  3. Methods from types referenced inMapper#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.

Before/After-mapping methods can also be used with builders:

  • @BeforeMapping methods with a@MappingTarget parameter of the real target will not be invoked because it is only available after the mapping was already performed.

  • To be able to modify the object that is going to be built, the@AfterMapping annotated method must have the builder as@MappingTarget annotated parameter. Thebuild method is called when the@AfterMapping annotated method scope finishes.

  • The@AfterMapping annotated method can also have the real target as@TargetType or@MappingTarget. It will be invoked after the real target was built (first the methods annotated with@TargetType, then the methods annotated with@MappingTarget)

13. Using the MapStruct SPI

To use a custom SPI implementation, it must be located in a separate JAR file together with a file named after the SPI (e.g.org.mapstruct.ap.spi.AccessorNamingStrategy) inMETA-INF/services/ 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).

It might also be necessary to add the jar to your IDE’s annotation processor factory path. Otherwise you might get an error stating that it cannot be found, while a run using your build tool does succeed.

13.1. Custom Accessor Naming Strategy

SPI name:org.mapstruct.ap.spi.AccessorNamingStrategy

MapStruct offers the possibility to override theAccessorNamingStrategy via the Service Provider Interface (SPI). A nice example is the use of the fluent API on the source objectGolfPlayer andGolfPlayerDto below.

Example 114. Source object GolfPlayer with fluent API.
1234567891011121314151617181920212223
publicclassGolfPlayer {privatedouble handicap;privateString name;publicdouble handicap() {return handicap;    }public GolfPlayer withHandicap(double handicap) {this.handicap = handicap;returnthis;    }publicString name() {return name;    }public GolfPlayer withName(String name) {this.name = name;returnthis;    }}
Example 115. Source object GolfPlayerDto with fluent API.
1234567891011121314151617181920212223
publicclassGolfPlayerDto {privatedouble handicap;privateString name;publicdouble handicap() {return handicap;    }public GolfPlayerDto withHandicap(double handicap) {this.handicap = handicap;returnthis;    }publicString name() {return name;    }public GolfPlayerDto withName(String name) {this.name = name;returnthis;    }}

We wantGolfPlayer to be mapped to a target objectGolfPlayerDto similar like we 'always' do this:

Example 116. Source object with fluent API.
12345678910
@MapperpublicinterfaceGolfPlayerMapper {    GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class );    GolfPlayerDto toDto(GolfPlayer player);    GolfPlayer toPlayer(GolfPlayerDto player);}

This can be achieved with implementing the SPIorg.mapstruct.ap.spi.AccessorNamingStrategy as in the following example. Here’s an implementedorg.mapstruct.ap.spi.AccessorNamingStrategy:

Example 117. CustomAccessorNamingStrategy
123456789101112131415161718192021222324
/** * A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the * form of {@code withProperty(value)}. */publicclassCustomAccessorNamingStrategyextends DefaultAccessorNamingStrategy {@Overridepublicboolean isGetterMethod(ExecutableElement method) {String methodName = method.getSimpleName().toString();return !methodName.startsWith("with" ) && method.getReturnType().getKind() != TypeKind.VOID;    }@Overridepublicboolean isSetterMethod(ExecutableElement method) {String methodName = method.getSimpleName().toString();return methodName.startsWith("with" ) && methodName.length() >4;    }@OverridepublicString getPropertyName(ExecutableElement getterOrSetterMethod) {String methodName = getterOrSetterMethod.getSimpleName().toString();return IntrospectorUtils.decapitalize( methodName.startsWith("with" ) ? methodName.substring(4 ) : methodName );    }}

TheCustomAccessorNamingStrategy makes use of theDefaultAccessorNamingStrategy (also available in mapstruct-processor) and relies on that class to leave most of the default behaviour unchanged.

Fore more details: The example above is present in our examples repository (https://github.com/mapstruct/mapstruct-examples).

13.2. Mapping Exclusion Provider

SPI name:org.mapstruct.ap.spi.MappingExclusionProvider

MapStruct offers the possibility to override theMappingExclusionProvider via the Service Provider Interface (SPI).A nice example is to not allow MapStruct to create an automatic sub-mapping for a certain type,i.e. MapStruct will not try to generate an automatic sub-mapping method for an excluded type.

TheDefaultMappingExclusionProvider will exclude all types under thejava orjavax packages.This means that MapStruct will not try to generate an automatic sub-mapping method between some custom type and some type declared in the Java class library.

Example 118. Source object
12345678910
publicclassSource {staticclassNestedSource {privateString property;// getters and setters    }private NestedSource nested;// getters and setters}
Example 119. Target object
12345678910
publicclassTarget {staticclassNestedTarget {privateString property;// getters and setters    }private NestedTarget nested;// getters and setters}
Example 120. Mapper definition
12345
@MapperpublicinterfaceErroneousCustomExclusionMapper {Target map(Source source);}

We want to exclude theNestedTarget from the automatic sub-mapping method generation.

Example 121. CustomMappingExclusionProvider
1234567891011121314151617
importjava.util.regex.Pattern;importjavax.lang.model.element.Name;importjavax.lang.model.element.TypeElement;importorg.mapstruct.ap.spi.MappingExclusionProvider;publicclassCustomMappingExclusionProviderimplements MappingExclusionProvider {privatestaticfinalPattern JAVA_JAVAX_PACKAGE =Pattern.compile("^javax?\\..*" );@Overridepublicboolean 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" ) );    }}

13.3. Custom Builder Provider

SPI name: org.mapstruct.ap.spi.BuilderProvider

MapStruct offers the possibility to override theDefaultProvider via the Service Provider Interface (SPI).A nice example is to provide support for a custom builder strategy.

Example 122. Custom Builder Provider which disables Builder support
1234567891011
importjavax.lang.model.type.TypeMirror;publicclassNoOpBuilderProviderimplements BuilderProvider {@Overridepublic BuilderInfo findBuilderInfo(TypeMirror type) {returnnull;    }}

13.4. Custom Enum Naming Strategy

SPI name:org.mapstruct.ap.spi.EnumMappingStrategy

MapStruct offers the possibility to override theEnumMappingStrategy via the Service Provider Interface (SPI).This can be used when you have certain enums that follow some conventions within your organization.For example all enums which implement an interface namedCustomEnumMarker are prefixed withCUSTOM_and the default value for them when mapping fromnull isUNSPECIFIED

Example 123. Normal Enum
1234
publicenum CheeseType {    BRIE,    ROQUEFORT;}
Example 124. Custom marker enum
123456
publicenum CustomCheeseTypeimplements CustomEnumMarker {    UNSPECIFIED,    CUSTOM_BRIE,    CUSTOM_ROQUEFORT;}

We wantCheeseType andCustomCheeseType to be mapped without the need to manually define the value mappings:

Example 125. Custom enum mapping
1234567
@MapperpublicinterfaceCheeseTypeMapper {    CheeseType map(CustomCheeseType cheese);    CustomCheeseType map(CheeseType cheese);}

This can be achieved with implementing the SPIorg.mapstruct.ap.spi.EnumMappingStrategy as in the following example.Here’s an implementedorg.mapstruct.ap.spi.EnumMappingStrategy:

Example 126. Custom enum naming strategy
123456789101112131415161718192021222324252627282930313233
publicclassCustomEnumMappingStrategyextends DefaultEnumMappingStrategy {@OverridepublicString getDefaultNullEnumConstant(TypeElement enumType) {if ( isCustomEnum( enumType ) ) {return"UNSPECIFIED";        }returnsuper.getDefaultNullEnumConstant( enumType );    }@OverridepublicString getEnumConstant(TypeElement enumType,String enumConstant) {if ( isCustomEnum( enumType ) ) {return getCustomEnumConstant( enumConstant );        }returnsuper.getEnumConstant( enumType, enumConstant );    }protectedString getCustomEnumConstant(String enumConstant) {if ("UNSPECIFIED".equals( enumConstant ) ) {return MappingConstantsGem.NULL;        }return enumConstant.replace("CUSTOM_","" );    }protectedboolean isCustomEnum(TypeElement enumType) {for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) {if ( typeUtils.asElement( enumTypeInterface ).getSimpleName().contentEquals("CustomEnumMarker" ) ) {returntrue;            }        }returnfalse;    }}

The generated code then for theCheeseMapper looks like:

Example 127. Generated CheeseTypeMapper
123456789101112131415161718192021222324252627282930313233343536373839404142
publicclassCheeseTypeMapperImplimplements CheeseTypeMapper {@Overridepublic CheeseType map(CustomCheeseType cheese) {if ( cheese ==null ) {returnnull;        }        CheeseType cheeseType;switch ( cheese ) {case UNRECOGNIZED: cheeseType =null;break;case CUSTOM_BRIE: cheeseType = CheeseType.BRIE;break;case CUSTOM_ROQUEFORT: cheeseType = CheeseType.ROQUEFORT;break;default:thrownewIllegalArgumentException("Unexpected enum constant:" + cheese );        }return cheeseType;    }@Overridepublic CustomCheeseType map(CheeseType cheese) {if ( cheese ==null ) {return CustomCheeseType.UNSPECIFIED;        }        CustomCheeseType customCheeseType;switch ( cheese ) {case BRIE: customCheeseType = CustomCheeseType.CUSTOM_BRIE;break;case ROQUEFORT: customCheeseType = CustomCheeseType.CUSTOM_ROQUEFORT;break;default:thrownewIllegalArgumentException("Unexpected enum constant:" + cheese );        }return customCheeseType;    }}

13.5. Custom Enum Transformation Strategy

SPI name:org.mapstruct.ap.spi.EnumTransformationStrategy

MapStruct offers the possibility to other transformations strategies by implementingEnumTransformationStrategy via the Service Provider Interface (SPI).A nice example is to provide support for a custom transformation strategy.

Example 128. Custom Enum Transformation Strategy which lower-cases the value and applies a suffix
123456789101112131415
importorg.mapstruct.ap.spi.EnumTransformationStrategy;publicclassCustomEnumTransformationStrategyimplements EnumTransformationStrategy {@OverridepublicString getStrategyName() {return"custom";    }@OverridepublicString transform(String value,String configuration) {return value.toLowerCase() + configuration;    }}

13.6. Additional Supported Options Provider

SPI name:org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider

MapStruct offers the ability to pass through declared compiler args (or "options") provided to the MappingProcessorto the individual SPIs, by implementingAdditionalSupportedOptionsProvider via the Service Provider Interface (SPI).

Example 129. Custom Additional Supported Options Provider that declaresmyorg.custom.defaultNullEnumConstant as an option to pass through
12345678910111213
importjava.util.Collections;importjava.util.Set;importorg.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;publicclassCustomAdditionalSupportedOptionsProviderimplements AdditionalSupportedOptionsProvider {@OverridepublicSet<String> getAdditionalSupportedOptions() {returnCollections.singleton("myorg.custom.defaultNullEnumConstant" );    }}

The value of this option is provided by including anarg to thecompilerArgs tag when defining your custom SPIimplementation.

Example 130. Example maven configuration with additional options
123456789101112
<configuration>    <annotationProcessorPaths>        <path>            <groupId>org.myorg</groupId>            <artifactId>custom-spi-impl</artifactId>            <version>${project.version}</version>        </path>    </annotationProcessorPaths>    <compilerArgs>        <arg>-Amyorg.custom.defaultNullEnumConstant=MISSING</arg>    </compilerArgs></configuration>

Your custom SPI implementations can then access this configured value viaMapStructProcessingEnvironment#getOptions().

Example 131. Accessing your custom options
1234567891011121314151617181920
importjavax.lang.model.element.TypeElement;importorg.mapstruct.ap.spi.DefaultEnumMappingStrategy;importorg.mapstruct.ap.spi.MapStructProcessingEnvironment;publicclassUnknownEnumMappingStrategyextends DefaultEnumMappingStrategy {privateString defaultNullEnumConstant;@Overridepublicvoid init(MapStructProcessingEnvironment processingEnvironment) {super.init( processingEnvironment );        defaultNullEnumConstant = processingEnvironment.getOptions().get("myorg.custom.defaultNullEnumConstant" );    }@OverridepublicString getDefaultNullEnumConstant(TypeElement enumType) {return defaultNullEnumConstant;    }}

14. Third-party API integration

14.1. Non-shipped annotations

There are various use-cases you must resolve ambiguity for MapStruct to use a correct piece of code.However, the primary goal of MapStruct is to focus on bean mapping without polluting the entity code.For that reason, MapStruct is flexible enough to interact with already defined annotations from third-party libraries.The requirement to enable this behavior is to match thename of such annotation.Hence, we say that annotation can befrom any package.

The annotationsnamed@ConstructorProperties and@Default are currently examples of this kind of annotation.

If such named third-party annotation exists, it does not guarantee its@Target matches with the intended placement.Be aware of placing a third-party annotation just for sake of mapping is not recommended as long as it might lead to unwanted side effects caused by that library.

A very common case is that no third-party dependency imported to your project provides such annotation or is inappropriate for use as already described.In such cases create your own annotation, for example:

123456789101112
packagefoo.support.mapstruct;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target(ElementType.CONSTRUCTOR)@Retention(RetentionPolicy.CLASS)public@interface Default {}

14.2. Lombok

MapStruct works together withProject Lombok as of MapStruct 1.2.0.Beta1 and Lombok 1.16.14.

MapStruct takes advantage of generated getters, setters, and constructors and uses them to generate the mapper implementations.Be reminded that the generated code by Lombok might not always be compatible with the expectations from the individual mappings.In such a case, either Mapstruct mapping must be changed or Lombok must be configured accordingly usinglombok.config for mutual synergy.

Lombok 1.18.16 introduces a breaking change (changelog).The additional annotation processorlombok-mapstruct-binding (Maven) must be added otherwise MapStruct stops working with Lombok.This resolves the compilation issues of Lombok and MapStruct modules.

<path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>0.2.0</version></path>

14.2.1. Set up

The set up using Maven or Gradle does not differ from what is described inSet up. Additionally, you need to provide Lombok dependencies.

Example 132. Maven configuration
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
<properties><org.mapstruct.version>1.6.3</org.mapstruct.version><org.projectlombok.version>1.18.16</org.projectlombok.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency><!-- lombok dependency should not end up on classpath --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version><scope>provided</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.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><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version></path><!-- additional annotation processor required as of Lombok 1.18.16 --><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>0.2.0</version></path></annotationProcessorPaths></configuration></plugin></plugins></build>
Example 133. Gradle configuration (3.4 and later)
123456789
dependencies {    implementation"org.mapstruct:mapstruct:${mapstructVersion}"    compileOnly"org.projectlombok:lombok:1.18.16"    annotationProcessor"org.projectlombok:lombok-mapstruct-binding:0.2.0"    annotationProcessor"org.mapstruct:mapstruct-processor:${mapstructVersion}"    annotationProcessor"org.projectlombok:lombok:1.18.16"}

The usage combines what you already know fromDefining a mapper and Lombok.

Example 134. Usage of MapStruct with Lombok
123456789101112131415161718192021222324252627
@DatapublicclassSource {privateString test;}publicclassTarget {privateLong testing;publicLong getTesting() {return testing;    }publicvoid setTesting(Long testing ) {this.testing = testing;    }}@MapperpublicinterfaceSourceTargetMapper {    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );@Mapping( source ="test", target ="testing" )Target toTarget(Source s );}

A working example can be found on the GitHub projectmapstruct-lombok.

Last updated 2024-11-09 11:31:02 UTC

[8]ページ先頭

©2009-2025 Movatter.jp