Skip to content

Silent default mapping on conflicting ignore properties flags #4094

Description

@Eliiona

Expected behavior

Upgrading form version 1.3.1.Final, where most mappings silently worked, this was hard to pinpoint.
I'd expect an exception if conflicting mappings are present in the list, instead of silent default to generated method.

Mapper with issue:

@Mapper(uses = PersonMapper.class)
public interface PersonInformationMapper {


    @Mapping(target = "type", source = "type")
    @Mapping(target = "relatedPerson.relatedInformation", ignore = true)
    @Mapping(target = "relatedPerson", source = "relatedPerson")
    @BeanMapping(ignoreByDefault = true)
    PersonInformationDto toDto(PersonInformation personInformation);

Nested Mapper:

@Mapper(uses = PersonMappingHelper.class)
public interface PersonMapper {

    @Mapping(target = "id", source = "id")
    @Mapping(target = "name", source = "name")
    @Mapping(target = "genderCode", source = "gender", qualifiedBy = PersonMappingHelper.Gender.class)
    @BeanMapping(ignoreByDefault = true)
    PersonDto toDto(Person person);

Clearly @Mapping(target = "relatedPerson.relatedInformation", ignore = true) is redundant here, however that should not have prevented PersonInformationMapper from using personMapper.toDto() to map related person. And not using it with no warning is the issue here.
Note: even qualifying toDto by name or annotation did not result in an exception.

Actual behavior

Here is the generated mapper implementation:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2026-07-14T11:33:11+0300",
    comments = "version: 1.6.3, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.8.jar, environment: Java 21.0.11 (Azul Systems, Inc.)"
)
public class PersonInformationMapperImpl implements PersonInformationMapper {

    @Override
    public PersonInformationDto toDto(PersonInformation personInformation) {
        if ( personInformation == null ) {
            return null;
        }

        PersonInformationDto personInformationDto = new PersonInformationDto();

        personInformationDto.setRelatedPerson( personToPersonDto( personInformation.getRelatedPerson() ) );
        personInformationDto.setType( personInformation.getType() );

        return personInformationDto;
    }

    protected PersonDto personToPersonDto(Person person) {
        if ( person == null ) {
            return null;
        }

        PersonDto personDto = new PersonDto();

        return personDto;
    }

when removing the redundant @Mapping(target = "relatedPerson.relatedInformation", ignore = true) mapstruct correctly uses PersonMapper.toDto(Person) instead.

Steps to reproduce the problem

Currently build folder has the wrong interpretation of mapper class.
You can comment out line 15 PersonInformationMapper.class and run again, then PersonMapper will be used.
mapstruct-test.zip

MapStruct Version

MapStruct 1.6.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions