Repository navigation
Silent default mapping on conflicting ignore properties flags #4094
Description
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
adityaanikam commentedon Jul 29, 2026
I looked into this. The repo is clear:
PersonInformationMappershould be able to delegaterelatedPersontoPersonMapper.toDto(Person)(which correctly mapsid,name,genderCode), but the redundant nested path@Mapping(target = "relatedPerson.relatedInformation", ignore = true)causes the processor to fall back to generating an inlinepersonToPersonDtomethod 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 treatrelatedPersonitself as needing its own bespoke handling, rather than recognizing that the nestedignoreonly applies torelatedPerson.relatedInformationspecifically and shouldn't disqualify delegation forrelatedPersonas a whole. I'll trace through thePropertyMapping/MappingResolverclasses 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.mapstruct#4094 Keep nested ignores when ignore-only path would direct…