'@AfterMapping mehod is not called in generated class

I'm trying to customize a mapping use a string to determine an object attribute so I wrote this:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ProductMapper {

public abstract ProductInput asProductInputFromIdentifier(String identifier);

@AfterMapping
protected void determineIdentifier(String identifier, @MappingTarget ProductInput out) {
    if (StringUtils.contains(identifier, '?')) {
        out.setExternalId(identifier);
    } else {
        out.setInernalId(identifier);
    }
}
}

The generated class doesn't call the determineIdentifier method. I found another solution by using directly the Java expression on the asProductInputFromIdentifier method but I really want to write a clear code using the @AfterMapping.

@Mapping(target = "externalId", expression = "java( org.apache.commons.lang3.StringUtils.contains(identifier, '|') ? identifier : null )")
@Mapping(target = "internalId", expression = "java( !org.apache.commons.lang3.StringUtils.contains(identifier, '|') ? identifier : null )")
public abstract ProductInput asProductDetailInputFromIdentifier(String identifier);

I didn't understand why it doesn't work Is it because I don't have an Object in method parameter?



Solution 1:[1]

MapStruct doesn't know how to map from String to ProductInput in your case I would suggest that you implement the mapping method on your own.

e.g.

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ProductMapper {

    public ProductInput asProductInputFromIdentifier(String identifier) {
        ProductInput out = new ProductInput();
        if (StringUtils.contains(identifier, '?')) {
            out.setExternalId(identifier);
        } else {
            out.setInernalId(identifier);
        }
        return out;
    }
}

There is no point in using lifecycle callbacks nor special @Mapping annotations to perform a simple mapping like in this example.

Solution 2:[2]

To solve the problem, I added the to ProductInput to the abstract method parameters as follow (which is not the requirement). As explains @Rakesh in his answer above Method with @AfterMapping will be executed at the end of map method and it should have same input parameter as map method, but it can interest others in their case:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ProductMapper {

   public abstract ProductInput asProductInputFromIdentifier(String identifier, ProductInput out);

   @AfterMapping
   protected void determineIdentifier(String identifier, @MappingTarget ProductInput out) {
      if (StringUtils.contains(identifier, '?')) {
          out.setExternalId(identifier);
      } else {
          out.setInernalId(identifier);
       }
    }
}

But In my case I used the builder = @Builder(disableBuilder = true) to turn off "builders" in MapStruct be it can help someone ;) Mapstruct Builder Now the generated MapperImpl calls the annotated @AfterMapping method.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Filip
Solution 2