2

Initial situation

Using current 1.5.0.Beta2 MapStruct release with JDK 13.

Domain Model

class Wrapper {
    private Fruit fruit;
}
abstract class Fruit {
    private int weight;
    /* ... */
}

class Apple extends Fruit {
    /* ... */
}

class Banana extends Fruit {
    /* ... */
}

(Corresponding 1:1 DTOs omitted)

Mappers

Mapper for Wrapper class

@Mapper(uses = {FruitMapper.class})
public interface WrapperMapper {
    WrapperDto map(Wrapper wrapper)
}

Mapper for Fruit class(es)

MapStruct Mapper for abstract Fruit class with method signature and annotations:

@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION /*...*/)
public interface FruitMapper {
    @SubclassMapping(source = Apple.class, target = AppleDto.class)
    @SubclassMapping(source = Banana.class, target = BananaDto.class)
    FruitDto map(Fruit fruit);
}

Problem

The above works fine until a field needs to be ignored on the referenced abstract class (e.g. the weight of a Fruit). Putting this annotation to the WrapperMapper map method...

@Mapping(target = "fruit.weight", ignore = true)
WrapperDto map(Wrapper wrapper)

...leads to The return type FruitDto is an abstract class or interface. Provide a non abstract / non interface result type or a factory method. compile error.

Question

Is there a way to skip fields in MapStruct mapping as outlined without getting this compile error?

1 Answer 1

4

Cause

What happens is that because you designate it on the WrapperMapper it will not directly use the FruitMapper. Instead the WrapperMapper implementation will get a new mapping method generated to map fruits. MapStruct fails at this attempt and gives you that compile error.

Solution

If you add that ignore to the FruitMapper it would inherit it to both subclassmappings:

        @SubclassMapping( source = Apple.class, target = AppleDto.class )
        @SubclassMapping( source = Banana.class, target = BananaDto.class )
        @Mapping( target = "weight", ignore = true )
        FruitDto map( Fruit fruit );

In case you want to have a mapping with weights and one without weights you could add the @org.mapstruct.Named annotation to the one without weights and then use this at the WrappedMapper.

like this:

    @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION /*...*/)
    public interface FruitMapper {
        @SubclassMapping( source = Apple.class, target = AppleDto.class )
        @SubclassMapping( source = Banana.class, target = BananaDto.class )
        FruitDto map( Fruit fruit );

        @SubclassMapping( source = Apple.class, target = AppleDto.class )
        @SubclassMapping( source = Banana.class, target = BananaDto.class )
        @Mapping( target = "weight", ignore = true )
        @Named( "NoWeights" )
        FruitDto map( Fruit fruit );
    }

    @Mapper(uses = {FruitMapper.class})
    public interface WrapperMapper {
        @Mapping( target = "fruit", qualifiedByName = "NoWeights" )
        WrapperDto map(Wrapper wrapper)
    }
Sign up to request clarification or add additional context in comments.

2 Comments

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
Many thanks @Ben, the solution with different mapping method with @org.mapstruct.Named annotation works like a charm!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.