Skip to content

Silent default mapping on conflicting ignore properties flags #4094

Open

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

Activity

  1. adityaanikam commented on Jul 29, 2026

    @adityaanikam

    I looked into this. The repo is clear: PersonInformationMapper should be able to delegate relatedPerson to PersonMapper.toDto(Person) (which correctly maps id, name, genderCode), but the redundant nested path @Mapping(target = "relatedPerson.relatedInformation", ignore = true) causes the processor to fall back to generating an inline personToPersonDto method instead one that maps nothing, silently. Removing that one annotation restores the correct delegation, confirming the nested-path ignore is what's throwing off the property mapping resolution.

    I'd like to work on this. My read is that the fix belongs in whatever logic decides "can I delegate to an existing/generatable mapper method for this target property" right now it looks like the presence of any nested path mapping under relatedPerson.* is making the processor treat relatedPerson itself as needing its own bespoke handling, rather than recognizing that the nested ignore only applies to relatedPerson.relatedInformation specifically and shouldn't disqualify delegation for relatedPerson as a whole. I'll trace through the PropertyMapping/MappingResolver classes to find exactly where that decision is made before proposing anything concrete. At minimum, this should raise an error rather than silently generating an under populated mapper silent data loss is the worse of the two failure modes here.

  2. added a commit that references this issue on Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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