You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Title: Compilation fails when combining source, qualifiedByName, and defaultValue in @mapping (Regression from 1.1.0.Final to 1.6.2)
Description
When using the @Mapping annotation with all three attributes (source, qualifiedByName, and defaultValue) together, MapStruct 1.6.2 fails to compile with a qualifier error. This combination worked correctly in MapStruct 1.1.0.Final.
Steps to Reproduce
Create a mapper with a mapping that combines source, qualifiedByName, and defaultValue
Try to compile the project
Expected Behavior
The code should compile successfully. The generated mapper should:
Call the qualified converter method when the source object is not null
Use the defaultValue when the source object is null
This was the behavior in MapStruct 1.1.0.Final.
Actual Behavior
Compilation fails with:
Qualifier error. No method found annotated with @Named#value: [ quantityConverter ].
See https://mapstruct.org/faq/#qualifier for more info.
Can't map "1" to "String quantity".
Should the combination of source, qualifiedByName, and defaultValue be supported?
If not supported, should there be a clearer error message?
Additional Context
This issue was discovered during a JDK 8 to JDK 17 migration where MapStruct was upgraded from 1.1.0.Final to 1.6.2. The code that previously compiled successfully now fails.
This is a common use case where:
A source object may be null
A custom converter is needed to extract/transform data
A sensible default value should be used when the source is null
A complete reproducible example is available in the attached MapStructIssueDemo.java file.
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
/**
* MapStruct Issue Demo: defaultValue with qualifiedByName compilation error
*
* Issue Description:
* When using @Mapping with source, qualifiedByName, and defaultValue together,
* MapStruct 1.6.2 fails to compile with error:
* "Qualifier error. No method found annotated with @Named#value: [ quantityConverter ]"
* "Can't map "1" to "String quantity"
*
* This works fine in MapStruct 1.1.0.Final but fails in 1.6.2
*/
public class MapStructIssueDemo {
// Source object
public static class SourceObject {
private String name;
private DataWrapper dataWrapper;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public DataWrapper getDataWrapper() {
return dataWrapper;
}
public void setDataWrapper(DataWrapper dataWrapper) {
this.dataWrapper = dataWrapper;
}
}
// Data wrapper
public static class DataWrapper {
private Integer count;
public Integer getCount() {https://docs.github.com/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
// Target object
public static class TargetObject {
private String name;
private String quantity;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getQuantity() {
return quantity;
}
public void setQuantity(String quantity) {
this.quantity = quantity;
}
}
// Converter class with @Named method
public static class DataConverter {
@Named("quantityConverter")
public String quantityConverter(DataWrapper dataWrapper) {
if (dataWrapper == null || dataWrapper.getCount() == null) {
return null;
}
return String.valueOf(dataWrapper.getCount());
}
}
// Mapper interface - THIS WILL FAIL TO COMPILE with MapStruct 1.6.2
@Mapper(uses = {DataConverter.class})
public interface IssueMapper {
IssueMapper INSTANCE = Mappers.getMapper(IssueMapper.class);
// This mapping causes compilation error in MapStruct 1.6.2
// Error: Qualifier error. No method found annotated with @Named#value: [ quantityConverter ]
// Error: Can't map "1" to "String quantity"
@Mapping(target = "name", source = "name")
@Mapping(target = "quantity", source = "dataWrapper", qualifiedByName = "quantityConverter", defaultValue = "1")
TargetObject convert(SourceObject source);
}
// Workaround 1: Remove defaultValue (THIS WORKS)
@Mapper(uses = {DataConverter.class})
public interface WorkaroundMapper1 {
WorkaroundMapper1 INSTANCE = Mappers.getMapper(WorkaroundMapper1.class);
@Mapping(target = "name", source = "name")
@Mapping(target = "quantity", source = "dataWrapper", qualifiedByName = "quantityConverter")
TargetObject convert(SourceObject source);
}
// Workaround 2: Handle default value in converter method
public static class DataConverterWithDefault {
@Named("quantityConverterWithDefault")
public String quantityConverterWithDefault(DataWrapper dataWrapper) {
if (dataWrapper == null || dataWrapper.getCount() == null) {
return "1"; // Return default value in converter
}
return String.valueOf(dataWrapper.getCount());
}
}
@Mapper(uses = {DataConverterWithDefault.class})
public interface WorkaroundMapper2 {
WorkaroundMapper2 INSTANCE = Mappers.getMapper(WorkaroundMapper2.class);
@Mapping(target = "name", source = "name")
@Mapping(target = "quantity", source = "dataWrapper", qualifiedByName = "quantityConverterWithDefault")
TargetObject convert(SourceObject source);
}
// Test main method
public static void main(String[] args) {
SourceObject source = new SourceObject();
source.setName("Test");
DataWrapper wrapper = new DataWrapper();
wrapper.setCount(5);
source.setDataWrapper(wrapper);
// Test with IssueMapper (will fail to compile)
// TargetObject target = IssueMapper.INSTANCE.convert(source);
// Test with WorkaroundMapper1
TargetObject target1 = WorkaroundMapper1.INSTANCE.convert(source);
System.out.println("Workaround 1 - Name: " + target1.getName() + ", Quantity: " + target1.getQuantity());
// Test with WorkaroundMapper2
TargetObject target2 = WorkaroundMapper2.INSTANCE.convert(source);
System.out.println("Workaround 2 - Name: " + target2.getName() + ", Quantity: " + target2.getQuantity());
// Test with null dataWrapper
source.setDataWrapper(null);
TargetObject target3 = WorkaroundMapper2.INSTANCE.convert(source);
System.out.println("Workaround 2 with null - Name: " + target3.getName() + ", Quantity: " + target3.getQuantity());
}
}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Title: Compilation fails when combining source, qualifiedByName, and defaultValue in @mapping (Regression from 1.1.0.Final to 1.6.2)
Description
When using the
@Mappingannotation with all three attributes (source,qualifiedByName, anddefaultValue) together, MapStruct 1.6.2 fails to compile with a qualifier error. This combination worked correctly in MapStruct 1.1.0.Final.Steps to Reproduce
Expected Behavior
The code should compile successfully. The generated mapper should:
This was the behavior in MapStruct 1.1.0.Final.
Actual Behavior
Compilation fails with:
Minimal Reproducible Example
Workarounds
Workaround 1: Remove defaultValue
Workaround 2: Handle default value in the converter method
Environment
Generated Code (MapStruct 1.1.0.Final - Working)
Questions
Additional Context
This issue was discovered during a JDK 8 to JDK 17 migration where MapStruct was upgraded from 1.1.0.Final to 1.6.2. The code that previously compiled successfully now fails.
This is a common use case where:
A complete reproducible example is available in the attached MapStructIssueDemo.java file.
Beta Was this translation helpful? Give feedback.
All reactions