javamapstruct

Explicitly referencing other mappers in MapStruct mapper


What if I need another mapper to create a mapping method implementation?

Since Java interfaces don't allow instance fields (nor should they), my mapper declaration must be an abstract class. However, it didn't help me much.

@Mapper(uses = EmailMapper.class,
        componentModel = "spring",
        injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public abstract class UserMapper {

    private final EmailMapper emailMapper; // another mapper

    protected UserMapper(EmailMapper emailMapper) {
        this.emailMapper = emailMapper;
    }

    public User toUser(UpdateUserRequestDto dto) {
        User user = new User();
        user.setId(dto.getId());
        // referencing another mapper
        user.setEmailData(emailMapper.toEmailDatas(dto.getEmailData(), user));
        return user;
    }
}
@Mapper(componentModel = "spring",
        injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface EmailMapper {

    @Mapping(source = "email", target = "email")
    @Mapping(source = "user", target = "user")
    EmailData toEmailData(String email, User user);

    default List<EmailData> toEmailDatas(List<String> emails, User user) {
        List<EmailData> emailData = emails.stream()
                .map(e -> toEmailData(e, user))
                .toList();
        return emailData;
    }
}

Here's what MapStruct generated:

@Component
public class UserMapperImpl extends UserMapper {

    private final EmailMapper emailMapper;

    @Autowired
    public UserMapperImpl(EmailMapper emailMapper) {

        this.emailMapper = emailMapper;
    }

It declared its own field instead of passing the mapper to super() as I intended.

So how do I reference other mappers in my mapping method implementations?


Solution

  • One solution is to declare the dependency mapper using setter instead of constructor.

    If you are using Lombok, you can write it like this:

    @Mapper(componentModel = "spring")
    public abstract class UserMapper {
    
        @Setter(onMethod_ = @Autowired)
        private EmailMapper emailMapper; // another mapper
    
        ...
    }
    

    Or, without Lombok:

    @Mapper(componentModel = "spring")
    public abstract class UserMapper {
    
        private EmailMapper emailMapper; // another mapper
    
        @Autowired
        public void setEmailMapper(EmailMapper emailMapper) {
            this.emailMapper = emailMapper;
        }
    
        ...
    }