Skip to content

Regression in MapStruct 1.6.x: Incorrect collection mapping code generation with custom mapper #3725

@bduisenov

Description

@bduisenov

Expected behavior

MapStruct should correctly generate the mapper implementation for converting UserEntity to UserModel, including mapping nested lists, without invoking methods intended for Optional types. The generated code should compile successfully, as it did in version 1.5.5.

Actual behavior

Starting from MapStruct version 1.6.0 (tested up to 1.6.2), the generated mapper implementation incorrectly uses the OptionalMapper.map methods when mapping List to List. This leads to compilation errors due to inappropriate method selection and type inference issues. Specifically, the generated code attempts to map a List using methods intended for Optional types, resulting in the following compilation error

error: no suitable method found for map(List<UserEntity.AddressEntity>,Class<Optional>)
    userModel.setAddresses( OptionalMapper.map( OptionalMapper.map( source.getAddresses(), Optional.class ) ) );
                             ^
    method OptionalMapper.<T>map(Optional<T>) is not applicable
      (cannot infer type-variable(s) T
        (argument mismatch; no instance(s) of type variable(s) exist so that Optional<T> conforms to Object))
    method OptionalMapper.<T>map(Object,Class<Optional<T>>) is not applicable
      (cannot infer type-variable(s) T
        (argument mismatch; Class<Optional> cannot be converted to Class<Optional<T>>))

Steps to reproduce the problem

import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.TargetType;

import java.util.List;
import java.util.Optional;

@Mapper(
    unmappedTargetPolicy = ReportingPolicy.ERROR,
    uses = OptionalMapper.class,
    injectionStrategy = InjectionStrategy.CONSTRUCTOR
)
public interface MainMapper {
    UserModel map(UserEntity source);
}

class UserEntity {
    List<AddressEntity> addresses;

    public List<AddressEntity> getAddresses() {
        return addresses;
    }

    public void setAddresses(List<AddressEntity> addresses) {
        this.addresses = addresses;
    }

    static class AddressEntity {
        List<String> addressLines;

        public List<String> getAddressLines() {
            return addressLines;
        }

        public void setAddressLines(List<String> addressLines) {
            this.addressLines = addressLines;
        }
    }
}

class UserModel {
    List<AddressModel> addresses;

    public List<AddressModel> getAddresses() {
        return addresses;
    }

    public void setAddresses(List<AddressModel> addresses) {
        this.addresses = addresses;
    }

    static class AddressModel {
        List<String> addressLines;

        public List<String> getAddressLines() {
            return addressLines;
        }

        public void setAddressLines(List<String> addressLines) {
            this.addressLines = addressLines;
        }
    }
}

interface OptionalMapper {
    static <T> T map(Optional<T> source) {
        return null;
    }

    static <T> Optional<T> map(Object source, @TargetType Class<Optional<T>> targetType) {
        return null;
    }
}

results with

public class MainMapperImpl implements MainMapper {

    @Override
    public UserModel map(UserEntity source) {
        if ( source == null ) {
            return null;
        }

        UserModel userModel = new UserModel();

        userModel.setAddresses( OptionalMapper.map( OptionalMapper.map( source.getAddresses(), Optional.class ) ) );

        return userModel;
    }
}

MapStruct Version

1.6.0-1.6.2

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions