Unlike most other bean mapping tools, MapStruct doesn’t work at runtime but is a compile-time code generator.
Generating mapping code at build time has many advantages:
Yes.
Check out the set-up instructions for Eclipse. There is also a work-in-progress Eclipse plug-in which facilitates the definition of mapper interfaces with auto-completion and some quick fixes.
Yes, as of MapStruct 1.2.0.Beta1 and Lombok 1.16.14.
Project Lombok is an annotation processor that (amongst other things) adds getters and setters to the AST (abstract syntax tree) of compiled bean classes. AST modifications are not foreseen by Java annotation processing API, so quite some trickery was required within Lombok as well as MapStruct to make both of them work together. Essentially, MapStruct will wait until Lombok has done all its amendments before generating mapper classes for Lombok-enhanced beans.
An example for using the two projects together can be found here.
If you are using Lombok 1.18.16 or newer you also need to add lombok-mapstruct-binding in order to make Lombok and MapStruct work together.
If you are on an older version of MapStruct or Lombok, the solution is to put the JavaBeans to be amended by Lombok and the mapper interfaces to be processed by MapStruct into two separate modules of your project. Then Lombok will run in the compilation of the first module, causing the bean classes to be complete when MapStruct runs during the compilation of the second module.
Check out that you are actually using org.mapstruct.Named
and not javax.inject.Named
.
This can happen if you are using mapstruct-jdk8
and
some other dependency is using an older version of mapstruct
.
To solve the problem find the dependency that is using mapstruct
and exclude it.
A known dependency that uses mapstruct
and has this problem is springfox-swagger2
.
For Maven you need to exclude it like:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger2.version}</version> <exclusions> <exclusion> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> </exclusion> </exclusions> </dependency>
For Gradle you need to exclude it like:
compile('io.springfox:springfox-swagger2:${swagger2.version}') { exclude group: 'org.mapstruct', module: 'mapstruct' }
Consider this (when thinking what MapStruct should do for updating collections in general):
About the latter one, many IDEs also generates remove methods. So should MapStruct call these in the light of the above?
At this moment it works like this: whenever the user wants a collection update method, MapStruct generates a regular call to element mappings (in stead of an update call), because it is the only sensible thing to do. All the remainder is highly dependent on the use-case.
The strategies were developed over time and hence the naming / behavior deserves attention in future versions of MapStruct to getter better allignment. This would introduce backward incompatibillties, so we cannot not do this in the 1.x versions of MapStruct.
The following table expresses when the current strategies apply:
source property | source bean | direct mapping | update mapping (@MappingTarget ) |
|
---|---|---|---|---|
NullValueCheckStrategy | x | x | x | |
NullValuePropertyMappingStrategy | x | x | ||
@Mapping#defaultValue |
x | x | x | |
NullValueMappingStrategy | x | x | x |
We’ve noticed a common mistake that NullValuePropertyMappingStrategy
is used in relation to direct mapping, which is understandble because of its naming. So lets look at the following example:
@Mapper public interface MyMapper { Bar map( Foo source ); } public class Foo { private String string; // setters/getters } public class Bar { private String string; // setters/getters }
generates:
public class MyMapperImpl implements MyMapper { @Override public Bar map(Foo source) { if ( source == null ) { return null; } Bar bar = new Bar(); bar.setString( source.getString() ); return bar; } }
So, lets look what it would mean if NullValuePropertyMappingStrategy
could be applied to direct mappings:
SET_TO_NULL
. What does this mean when the source.string
is null? It would set the source to null when null. But that is what it already does above without any strategy.
IGNORE
. When source.string
is null, it would ignore setting the target. But that can be achieved with NullValueCheckStrategy.ALWAYS
. Look at the example below:
@Mapper( nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS ) public interface MyMapper { Bar map( Foo source ); }
generates:
public class MyMapperImpl implements MyMapper { @Override public Bar map(Foo source) { if ( source == null ) { return null; } Bar bar = new Bar(); if ( source.getString() != null ) { bar.setString( source.getString() ); } return bar; } }
SET_TO_DEFAULT
. SET_TO_DEFAULT
is not covered by other cases in direct mapping, but can be achieved as well. Lets asume we would like to set an empty String as default value on null source. There are 2 possibilities:@Mapper public interface MyMapper { @Mapping( target = "string", defaultValue = "" ) Bar map( Foo source ); }
generates:
public class MyMapperImpl implements MyMapper { @Override public Bar map(Foo source) { if ( source == null ) { return null; } Bar bar = new Bar(); if ( source.getString() != null ) { bar.setString( source.getString() ); } else { bar.setString( "" ); } return bar; } }
but has the drawback that this needs to be done for each property.
The other option would be to create the target object with a default property value, either inside the target (Bar) during construction -if you have control over the target beans-, or via an object factory method:
@Mapper( nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) public interface MyMapper { Bar map( Foo source ); @ObjectFactory default Bar create() { Bar bar = new Bar(); bar.setString( "" ); return bar; } }
Finally overview below shows on what level a strategy can be applied:
@MapperConfig | @Mapper | @BeanMapping | @Mapping | |
---|---|---|---|---|
NullValueCheckStrategy | x | x | x | x |
NullValuePropertyMappingStrategy | x | x | x | x |
@Mapping#defaultValue | x | |||
NullValueMappingStrategy | x | x | x |
More detailed information can be found in the reference guide.
MapStruct tries various mechanisms to map a sourceproperty to a targetproperty when it cannot make a direct mapping. In order, MapStruct tries:
target = methodY( methodX ( source ) )
target = methodY( conversionX ( source ) )
target = conversionY( methodX ( source ) )
Whenever MapStruct finds a unique candidate, MapStruct stops and uses this method to make the mapping between source and target. However, for option 1, 5, 6, 7 it is possible that multiple eligible candidates are found for which MapStruct cannot decide which one to select. MapStruct reports this as “ambiguous mapping method” and lists the methods from which it cannot make a selection. Here, you have to guide MapStruct in making the correct mapping.
This can be done in the following ways:
MapStruct uses the mechanism of Qualifiers to resolve conflicts. MapStruct selects methods based on the combination of source type and target type.
An error labeled: “Qualifier error” is MapStructs way of letting you know that it cannot find the method you intended to annotate with a qualifier annotation or with @Named
. There can be several reasons for this:
@Retention(RetentionPolicy.CLASS)
@Named
) to the designated methodMapStruct selects methods by means of assignabillity of source - and target type:
@MappingTarget
annotated target parameter of a method.@Mapper#used
mappers or in the Mapper
class/interface itself.Problems arise when more than one method meets the qualifications.
In general, qualifiers are used to guide MapStruct to the proper choice. Usualy by indicating @Mapping#qualifiedBy
or @Mapping#qualifiedByName
. Lesser known is that Qualifiers also work the other way around: if a method is annotated with a qualfier that does not match the @Mapping#qualifiedBy
MapStruct will not select that method. This is also valid when @Mapping#qualifiedBy
is absent alltogether
Consider specifying a qualifier like this:
@Qualifier // make sure that this is the MapStruct qualifier annotation @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface DoIgnore { }
and placing it on a method:
@DoIgnore TypeX doSomething(TypeY y) { // -- do something }
the method doSomething
will be ignored by MapStruct.
The unfortunate short answer: You can’t.
Spring’s Converter definition
only allows for a convert
method that has a single input parameter (the source object) and returns the converted (mapped) response.
If you ever find yourself in a situation where you’d like to combine Spring’s Converter
and MapStruct’s @context
concepts, your options are:
@Context
object as parameter, consider injecting it.@Context
object at runtime.@Context
object, you should
stick to MapStruct’s core functionality and refrain from letting this particular Mapper extend the Converter
interface.