Skip to content

Camelcase fields where the second letter is capitalized are not picked up #4000

@MeHereRandom

Description

@MeHereRandom

Expected behavior

If a field name like xNameField is present in both source and target, MapStruct should recognize that field and generate a mapping for it, no matter whether the target class is using builders or not.

Actual behavior

If the target class provides a builder (generated via Lombok's @Builder) with a method TargetClass.TargetClassBuilder.xNameField(), MapStruct ignores that field completely.

Removing the builder from the target class or telling MapStruct not to use it (builder = @Builder(disableBuilder = true)) resolves the problem, although in the latter case, the generated code still is a bit weird:

    public TargetClass map(SourceClass src) {
        if ( src == null ) {
            return null;
        }

        String someField = null;
        String someOtherField = null;

        someField = src.getSomeField();
        someOtherField = src.getSomeOtherField();

        String xNameField = null;

        TargetClass targetClass = new TargetClass( someField, xNameField, someOtherField );

        targetClass.setXNameField( src.getXNameField() );

        return targetClass;
    }

What also works is adding an explicit mapping with intentionally wrong capitalization, @Mapping(target = "xNameField", source = "XNameField") generates the correct mapping when it clearly shouldn't -- while @Mapping(target = "xNameField", source = "xNameField") causes an error "No property named "xNameField" exists in source parameter(s). Did you mean "XNameField"?"

Steps to reproduce the problem

Mapping source:

import lombok.Data;

@Data
public class SourceClass {
	private String someField;
	private String xName;
	private String someOtherField;
}

Mapping target:

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class TargetClass {
	private String someField;
	private String xNameField;
	private String someOtherField;
}

...and a simple mapper:

import org.mapstruct.Mapper;

@Mapper
public interface PocMapper {
	TargetClass map(final SourceClass src);
}

Result is:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2026-02-20T10:51:20+0100",
    comments = "version: 1.6.3, compiler: javac, environment: Java 21.0.7 (Eclipse Adoptium)"
)
public class PocMapperImpl implements PocMapper {

    @Override
    public TargetClass map(SourceClass src) {
        if ( src == null ) {
            return null;
        }

        TargetClass.TargetClassBuilder targetClass = TargetClass.builder();

        targetClass.someField( src.getSomeField() );
        targetClass.someOtherField( src.getSomeOtherField() );

        return targetClass.build();
    }
}

What works

  • Removing the @Builder from TargetClass
  • @Mapper(builder = @Builder(disableBuilder = true)), although MapStruct still seems to be a bit confused (see previous section)

What works, even though it clearly should not

  • adding @Mapping(target = "xNameField", source = "XNameField"), with a capital X in the source

What makes it even worse, even though it should work

  • adding @Mapping(target = "xNameField", source = "xNameField"), with the correct lower-case x in the source, lead to an error with the helpful suggestion to use "XNameField" instead

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