We use an immutable structure to create all DTOs. Now we want to map these objects to each other using mapstruct . But the generated DTOs are immutable and have no setters and no constructor that matches the builder pattern. They are filled only through the appropriate builder, accessed by the static builder() method.
Instead, we tried to map DTO1 to DTO2.Builder, which would work if mapstruct recognized the setter in Builder, but they did not have a return type, but returned Builder itself for free concatenation.
So here is an example code.
We have two interfaces
@Value.Immutable public interface MammalDto { public Integer getNumberOfLegs(); public Long getNumberOfStomachs(); }
and
@Value.Immutable public interface MammalEntity { public Long getNumberOfLegs(); public Long getNumberOfStomachs(); }
Then we have the Mapper interface for mapstruct:
@Mapper(uses = ObjectFactory.class) public interface SourceTargetMapper { SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class ); ImmutableMammalEntity.Builder toTarget(MammalDto source); }
For mapstruct to find Builder, we need Factory:
public class ObjectFactory { public ImmutableMammalDto.Builder createMammalDto() { return ImmutableMammalDto.builder(); } public ImmutableMammalEntity.Builder createMammalEntity() { return ImmutableMammalEntity.builder(); } }
To generate the code, the compiler plugin was instructed to use both annotation handlers:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.immutables</groupId> <artifactId>value</artifactId> <version>2.2.8</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.2.0.Beta3</version> </path> </annotationProcessorPaths> </configuration> </plugin>
Note. This will only work with mapstruct> 1.2.x. Older versions have a problem in the clean build ( mvn clean compile ) that they cannot find sources that are just built-in. In the second build (without clearing), they will find an immutables implementation because they were on the class path before running annotation handlers. Now this error is fixed.
It works like a charm. First, Invariable implementations of interfacts are generated, and mapstruct uses them to create a builder.
But the test shows that no properties are set:
@Test public void test() { MammalDto s = ImmutableMammalDto.builder().numberOfLegs(4).numberOfStomachs(3l).build(); MammalEntity t = SourceTargetMapper.MAPPER.toTarget(s).build(); assertThat(t.getNumberOfLegs()).isEqualTo(4); assertThat(t.getNumberOfStomachs()).isEqualTo(3); }
Statements fail. One look at the handler generated by mapstruct shows that it clearly did not find any setters:
@Generated( value = "org.mapstruct.ap.MappingProcessor", //... ) public class SourceTargetMapperImpl implements SourceTargetMapper { private final ObjectFactory objectFactory = new ObjectFactory(); @Override public Builder toTarget(MammalDto source) { if ( source == null ) { return null; } Builder builder = objectFactory.createMammalEntity(); return builder; } }
The empty constructor is returned. I think the reason is the setter implementation of the generated builder, because it returns itself to create a free API:
public final Builder numberOfLegs(Long numberOfLegs) { this.numberOfLegs = Objects.requireNonNull(numberOfLegs, "numberOfLegs"); return this; }
Is there any way that mapstruct can find these setters? Or even the best way to deal with such immutable objects with builders?
EDIT: As I said in the comment, I came across Issue # 782 . In version 1.2.0.Beta3, builders are still not supported. But there are several discussions on this topic, so it would be interesting to keep track of the problem if you have the same problem.