Skip to content

@Condition treated as ambiguous mapping for methods returning Boolean/boolean #3565

@hmzuleta

Description

@hmzuleta

Expected behavior

If I have a method annotated with @Condition that serves as a presence check for Java Optionals, and I have a separate mapping method that unboxes an Optional, the Condition method should not be treated as a valid mapper method.

Actual behavior

Conditional method, which needs to return boolean, clashes with mapping methods that return Boolean and whose input argument type is the same, e.g.

@Condition
boolean conditionalMethod(TypeA typeAObject) { }

clashes with

@Mapper
interface MyMapper {
boolean mappingMethod(TypeA typeAObject);
}

Specifically, this is true when attempting to use a Conditional method to check the if the Boolean wrapped in an Optional isPresent, because it will return a boolean, while the mapping method that unwraps the Boolean also does this.

Steps to reproduce the problem

MapStruct 1.5.3, Spring Boot 3.1.6

import org.mapstruct.*;
import java.util.Optional;

@Mapper
interface MyMapper {
    default <T> T mapFromOptional(Optional<T> value) { return value.orElse((Object) null); }
    
    @Condition
    default boolean isOptionalPresent(Optional value) { return value.isPresent(); }

    MyTargetDTO map(MySourceDTO sourceDto);
}

class MySourceDTO {
    private Boolean isCondition;
    public Optional<Boolean> getIsCondition() { return Optional.ofNullable(this.isCondition); }
}

class MyTargetDTO {
    private String isCondition;
    public Optional<String> getIsCondition() { return Optional.ofNullable(this.isCondition); }
    public void setIsCondition(String isCondition) { this.isCondition = isCondition; }
}

Result:

Ambiguous 2step methods found, mapping Optional sourceDto to String. Found conversionY( methodX ( parameter ) ): conversionY: Boolean -->String, method(s)X: T mapFromOptional(Optional value); conversionY: boolean-->String, method(s)X: T mapFromOptional(Optional value);

It seems to me like MapStruct is taking the @Condition method, and using it as a valid mapping method, instead of using it only for Conditional Mapping presence checking.
If I understand correctly, the mapping steps would be:

  1. Call the @Mapper's map, to convert the MySourceDTO to MyTargetDTO.
  2. Since the getter returns an Optional<Boolean>, it needs to call the @Mapper's mapFromOptional, for the sourceDto.isCondition property.

This will resolve the mapping as default Boolean mapFromOptional(Optional<Boolean> value), which has a similar signature as default boolean isOptionalPresent(Optional value), so that must look ambiguous to MapStruct. However, the latter method is NOT a mapping method, merely a condition.

Methods annotated with @Condition should NOT be considered for mappings.

MapStruct Version

MapStruct 1.5.3

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions