15

I am using mapstruct to map from one DTO to another. I have multiple default methods , but 2 of them with a return value of String and that uses the same class as the input parameter gives me "Ambiguous mapping methods using java Mapstruct" error. I am adding the relevant parts of the code here:

@Mappings({
     @Mapping(source = "programInstance", target = "title", qualifiedByName = "title"),
     @Mapping(source = "programInstance", target = "seriesName", qualifiedByName = "seriesName"),
     @Mapping(source = "programInstance", target = "season", qualifiedByName = "season"),
     @Mapping(source = "programInstance", target = "epNumber", qualifiedByName = "epNumber"),
 })
 DTO1 mapDTOs (DTO2 dto2);

  @Named("title")
default String mapTitle(Program programInstance) {
    Optional<String> title = Utils.getObject(() -> programInstance.getTitle().getDescriptions().get(0).getValue());
    if (title.isPresent())
        return title.get();
    return null;
}
@Named("seriesName")
default String mapSeriesName(Program programInstance) {
    Optional<String> seriesName = Utils.getObject(() -> programInstance.get(0).getProgram().getTitle().getDescriptions().get(0).getValue());
    if (seriesName.isPresent())
        return seriesName.get();
    return null;
}
 @Named("season")
default Integer mapSeasonNumber(Program programInstance) {
    Optional<Integer> season = Utils.getObject(() -> programInstance.get(0).getSeasonOf().get(0).getOrderNo());
    if (season.isPresent())
        return season.get();
    return null;
}

@Named("epNumber")
default Integer mapEpNumber(Program programInstance) {
    Optional<Integer> epNumber = Utils.getObject(() -> programInstance.getEpOf().get(0).getOrderNo());
    if (epNumber.isPresent())
        return epNumber.get();
    return null;
}

The error is

Ambiguous mapping methods found for mapping property "Program programInstance" to java.lang.String: java.lang.String mapTitle(), java.lang.String mapSeriesName().

ljs
  • 495
  • 1
  • 8
  • 23
  • 1
    Are you sure that you are using `org.mapstruct.Named` and not some other one? – Filip Feb 11 '19 at 06:31
  • @Filip Yes I am using org.mapstruct.Named – ljs Feb 11 '19 at 06:39
  • Just verified your example on our 1.3 code base.. It works – Sjaak Feb 11 '19 at 18:41
  • 1
    but.. I also spotted the name of the method deviates.. `mapSeriesName`.. Your example has a signature `mapSeriesName1 `.. Do you by any chance point to a used mapper? Are there more methods that carry the same `@Named`? – Sjaak Feb 11 '19 at 18:43
  • sjaak: that was a type, sorry, edited the code. My version was 1.2, tried changing to 1.3 now - the error still exists.. Yes, I have more @Named methods that carry the same source Program.. – ljs Feb 11 '19 at 19:16
  • edited code to add the other methods that use Program as the source – ljs Feb 11 '19 at 19:24
  • just out of curiosity, if you add ignore=true to the 2 String mappings.. does MapStruct complain about the Integer mappings as well? – Sjaak Feb 11 '19 at 21:49
  • Is there anyway (e.g. gist) that you can share the DTO's as well. I can give it a shot tomorrow.. – Sjaak Feb 11 '19 at 21:51
  • Yes, it does complain about the Integer mappings – ljs Feb 11 '19 at 22:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/188239/discussion-between-ljs-and-sjaak). – ljs Feb 11 '19 at 23:40

5 Answers5

8

I checked your example.. The problem is that the fields you try to target are of type String.

So:

public class IvpVodOfferStatusDTO {

    private String seasonNumber;
    private String episodeNumber;
} 

MapStruct tries to match this with the signature you provide:

 @Named("season")
default Integer mapSeasonNumber(Program programInstance) {
    Optional<Integer> season = Utils.getObject(() -> programInstance.get(0).getSeasonOf().get(0).getOrderNo());
    if (season.isPresent())
        return season.get();
    return null;
}

@Named("epNumber")
default Integer mapEpNumber(Program programInstance) {
    Optional<Integer> epNumber = Utils.getObject(() -> programInstance.getEpOf().get(0).getOrderNo());
    if (epNumber.isPresent())
        return epNumber.get();
    return null;
}

MapStruct has a predefined order of attempts:

  1. User provided Mapping method
  2. Direct (types source -target are the same)
  3. Mapping method (built-in)
  4. Type conversion

If this all fails MapStruct tries to do a number of 2 step approaches:

  1. mapping method - mapping method
  2. mapping method - type conversion
  3. type conversion - mapping method

At 6. it finds 2 qualifying methods (Program to String). It's probably an error in MapStruct that it selects methods that do not qualify (need to check whether this is intentional) by the @Named. Otherwise, I'll write an issue.

The most easy solution is: adapt the target:

public class IvpVodOfferStatusDTO {

    private Integer seasonNumber;
    private Integer episodeNumber;
}

What is probably what you intend (I guess).. Otherwise you could change the signature not to return an Integer but a String

Federico Piazza
  • 30,085
  • 15
  • 87
  • 123
Sjaak
  • 3,602
  • 17
  • 29
4

Even if the data types are matching, this could happen if the name given at qualifiedByName is not defined in as a bean instance

Because without a matching @Named qualifier, the injector would not know which bean to bind to which variable

@Mapping( source = "firstName", target = "passenger.firstName", qualifiedByName = "mapFirstName" )
public abstract Passenger mapPassenger( Traveller traveller );

@Named( "mapFirstName" )
String mapFirstName( String firstName)
{
}
2

I was facing same issue and observed that, there was same method inherited by my mapper class using @Mapper(uses = {BaseMapper.class}) and using extends BaseMapper. Removing extends solved the problem for me. So, you can look for method received by custom mapper through multiple ways.

0

Also note you may get this if you have cyclic dependency:

ObjectA -> ObjectB -> ObjectA -> ObjectB

It will be reported as an ambiguous entry most of the time. The solution is to not refer to Object A in ObjectB.

PeterS
  • 2,818
  • 23
  • 36
0

Ambiguous mapping methods can also happen if a @Named("name") method annotation is duplicated with the same "name": They need to be unique across @Named methods.

cellepo
  • 4,001
  • 2
  • 38
  • 57