Beyond Basics: Collection Mapping and Custom Mappers


Mapping Lists, Sets and Maps

MapStruct provides built-in support for mapping collections such as List, Set, and even Map. It will automatically map each element using the corresponding element mapping method.


List<UserDto> toDtoList(List<User> users);
Set<RoleDto> toRoleDtoSet(Set<Role> roles);
Map<String, AddressDto> toAddressDtoMap(Map<String, Address> addresses);

If a mapping method for the element type exists, MapStruct will use it recursively. This simplifies batch transformation significantly.

Using @BeforeMapping and @AfterMapping

Sometimes, additional logic is needed before or after the mapping occurs. MapStruct allows you to define methods annotated with @BeforeMapping and @AfterMapping to hook into the process.


@AfterMapping
default void enrich(@MappingTarget UserDto dto, User entity) {
    dto.setFullName(entity.getFirstName() + " " + entity.getLastName());
}

These lifecycle methods are ideal for:

  • Populating derived fields
  • Logging or monitoring transformations
  • Injecting default values

Creating and Using Decorators

Decorators in MapStruct provide a way to wrap and extend the generated mapper with custom logic. This is useful when your mapping logic becomes too complex for annotations alone.


// Mapper interface
@Mapper(componentModel = "spring")
@DecoratedWith(UserMapperDecorator.class)
public interface UserMapper {
    UserDto toDto(User user);
}

// Decorator class
@Component
public class UserMapperDecorator implements UserMapper {
    private final UserMapper delegate;

    public UserMapperDecorator(UserMapper delegate) {
        this.delegate = delegate;
    }

    @Override
    public UserDto toDto(User user) {
        UserDto dto = delegate.toDto(user);
        dto.setFullName(user.getFirstName() + " " + user.getLastName());
        return dto;
    }
}

Decorators offer full control while still benefiting from MapStruct's generated code.

Handling Null Values Gracefully

MapStruct gives you options to control how null values are treated. By default, if the source object is null, the target will also be null. You can override this behavior using strategies:


@Mapper(
    componentModel = "spring",
    nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,
    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
)

These strategies help you:

  • Avoid NullPointerExceptions
  • Preserve existing values on partial updates
  • Ensure collections are initialized

Conclusion

Going beyond basic field mapping, MapStruct equips developers with powerful tools to handle collections, enrich mapping behavior, and gracefully manage nulls. With features like decorators and lifecycle hooks, you can build clean, reusable, and testable mappers that scale with your application complexity.

Post a Comment

0 Comments