25

I'm having trouble using Mapstruct.

I am using a @Mapper annotated interface with @AfterMapping inside like follow:

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ConfiguracionReautorizacionMapper {

    ConfiguracionReautorizacionDTO toConfiguracionReautorizacionDTO(final ConfiguracionReautorizacion configuracionReautorizacion);

    ConfiguracionReautorizacion toConfiguracionReautorizacion(final ConfiguracionReautorizacionDTO configuracionReautorizacionDTO);

    @AfterMapping
    default void fillServiciosAsociados(@MappingTarget final ConfiguracionReautorizacionDTO configuracionReautorizacionDTO, final ConfiguracionReautorizacion configuracionReautorizacion) {
        configuracionReautorizacionDTO.setTieneRolesOServiciosAsociados(!(CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk()) && CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk())));
    }

}

The mapper works perfectly but the @AfterMappingmethod is never called. I read other post that shows examples using abstract class instead of interface.

Is using abstract class mandatory for use @AfterMapping annotation?

Jose A. Matarán
  • 1,044
  • 3
  • 13
  • 33

6 Answers6

33

You can't pass the object (that is assumed to be immutable). You should pass the builder.. like this:

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ConfiguracionReautorizacionMapper {

    ConfiguracionReautorizacionDTO toConfiguracionReautorizacionDTO(final ConfiguracionReautorizacion configuracionReautorizacion);

    ConfiguracionReautorizacion toConfiguracionReautorizacion(final ConfiguracionReautorizacionDTO configuracionReautorizacionDTO);

    @AfterMapping
    default void fillServiciosAsociados(@MappingTarget final ConfiguracionReautorizacionDTO.Builder configuracionReautorizacionDTO, final ConfiguracionReautorizacion configuracionReautorizacion) {
        configuracionReautorizacionDTO.setTieneRolesOServiciosAsociados(!(CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk()) && CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk())));
    }

}

Checkout out MapStruct issue 1556.. You can also disable builders from 1.3.1 onwards

Sjaak
  • 3,602
  • 17
  • 29
  • 5
    This one solved my issue. I had to remove @Builder annotation from my class. – Clawdidr Feb 19 '21 at 11:52
  • 2
    @Clawdidr This could be an answer: so the ` @Builder` annotation make mapstruct `@AfterMapping` fail ! – pdem May 26 '21 at 16:38
  • 2
    thanks. this work for my case. but it's useless when we pass builder to aftermapping, i need to update base class not builder one – uncle bob Jul 15 '21 at 16:04
15

It is a bug when using Lombok#@Builder and Mapstruct@AfterMapping

Please use this situation @BeanMapping(builder = @Builder(disableBuilder = true)).

see https://github.com/mapstruct/mapstruct/issues/1556#issuecomment-1011493330

Xuanyu
  • 661
  • 5
  • 12
6

When you are using Lombok builder and mapstruct together. @AfterMapping is not much useful though you pass a Builder object, since we can't retrieve the processed values. Instead I have used the custom method in my mapper to resolve this issue.

@Mapping(target ="field", expression = "java(customMethod(obj))")

this solved my use case, hope it helps others too.

raji
  • 401
  • 1
  • 8
  • 17
  • 1
    interesting, though you can only access input params and not work on your target object with already mapped values as in @AfterMapping, which I need to finalize a collection from other mapped values. – Gregor Jan 31 '22 at 12:14
  • That's an Interesting requirement though but I am not sure for your use case. Post me if you got to solve this use case. – raji Feb 05 '22 at 08:18
  • In the end, I removed the builder because it was the cleanest solution and the builder was only used in some test code. – Gregor Feb 19 '22 at 22:33
  • If you're using lombok with mapstruct on the same beans, you have to use lombok-mapstruct-binding. Just add it as a dependency to your project and it will work. For more info: https://mapstruct.org/faq/#Can-I-use-MapStruct-together-with-Project-Lombok – Mohamed El-Beltagy May 23 '22 at 17:56
0

This is a comment but can't fit in the comment section.

If someone is gonna use @raji's answer, you must pay attention to the name of the variable you pass to customMethod

instead of obj you must put the name of the variable in your mapper method.

for Example:

@Mapping(target ="name", expression = "java(resolveName(userModel))")
UserDTO toDTO(User userModel);


default String resolveName(final User model) {
    return String.format("%s %s", model.firstName, model.lastName);
}
Tarun Kolla
  • 917
  • 1
  • 10
  • 30
moolsbytheway
  • 1,152
  • 13
  • 20
0

As mentioned in the comments the issue is because you are likely using Lombok Builder annotation for ConfiguracionReautorizacionDTO and ConfiguracionReautorizacion and possibly any of the nested classes of these classes. AfterMapping functionality does not play well with Lombok Builder. Again Lombok generally works with MapStruct, it seems Builder annotation is a specific issue. See if you can create the classes without using Builder annotation and then AfterMapping functionality should work as expected.

0

Thanks to @Sjaak answer which is the legit answer. For my case this worked:

 myAfterMapping(...,@MappingTarget MyBean.MyBeanBuilder<?, ?> myBean,...){
 myBean.id(someValue);
 }

MyBeanBuilder is autogenerated (@Builder).

LovaBill
  • 5,107
  • 1
  • 24
  • 32