MapStruct — Advanced Concepts and Dependency Injection

Akshay Jain
Globant
Published in
7 min readJun 4, 2021

Introduction

In my previous blog, we looked at how to set up MapStruct with its basic mapping scenarios. If you are new to MapStruct and haven’t gone through it yet, I highly recommend you to browse through it before proceeding with this one…

Welcome back, let’s now see a few advanced mapping configurations that give us all the flexibility to configure the special behavior based on our business needs and also integration with Spring Dependency Injection.

So let’s get started -

• Mapping With Type Conversion:

Mapstruct offers a seamless type conversion implicitly. Some of the supported conversions are -

  • Between all the Java primitive data types and their corresponding wrapper types, e.g. between int and Integer.
  • Within all the Java primitive number types and the wrapper types, e.g. between int and long.
  • Between all the Java primitive types (including their wrappers) and String.
  • Between enum types and String.
  • Between different types of Date objects and String.

Note: Converting from larger data types to smaller ones (e.g. from long to int) can cause a value or precision loss. The Mapper annotation has a method typeConversionPolicy to control warnings / errors.

Let’s take an example of conversion between Date and String. To achieve this our Mapper will look like -

Ignoring Fields From Target Bean:

There can be scenarios when certain attributes should not be propagated from source to target. To ignore such property all we have to do is -

Mapping Default Values and Constants:

There can be scenarios where we want to put some default values if the source field is null or if we want to put a constant value always for a specific target field. To achieve this our mapper will look like -

Updating Existing Bean Instances:

In some cases you need mappings which don’t create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with @MappingTarget. The following shows an example -

Note — The return type of method is void this time.

Mapping Using Java Expressions:

MapStruct gives us flexibility to include Java code constructs while providing the field mapping as the entire source object is available for usage in the expression.

Care should be taken to insert only valid Java code, as MapStruct will not validate the expression at generation-time, but errors will show up in the generated classes during compilation.

Consider an example where we are providing a random id to one of the target fields.

Important — In case your expression has reference to some other classes, then you need to specify all such classes in the imports method of @Mapper annotation like —

@Mapper(imports = UUID.class)

or

@Mapper(imports = {UUID.class, Date.class})

Mapping Methods Selection Using Qualifiers:

There can be cases where you have to perform some complicated steps to map two fields. In such cases we can use the qualifiedByName method of @Mapping annotation to specify the method which should be invoked to perform that mapping.

  • If using Java 8 and later versions, they can be defined as default or static methods within the same interface.
  • If using a Java version older than Java 8, you can define all the mappings in an Abstract class instead of the Interface.

The method that contains logic must be annotated with the annotation @Named and it must contain the same name as the string you provided to the qualifiedByName method.

Let’s see a simple example for better understanding where we are just converting dollars into cents -

Mapping Collections:

The mapping of collection types (List, Set etc.) is done in the same way as mapping bean types, i.e. by defining mapping methods with the required source and target types in a mapper interface.

The generated code will contain a loop which iterates over the source collection, converts each element and puts it into the target collection.

Let’s take a look at below Mapper -

In the above mapper we have declared mapping methods for converting List to List, Set to Set and List to Set, MapStruct supports a wide range of iterable types from the Java Collection Framework.

In the similar fashion, we can define a mapper for Map. The following shows an example:

Take a few minutes to ponder over the code above. Did you notice anything different???

Yes, you are absolutely right. Instead of our usual @Mapping annotation, we have used a special annotation @MapMapping here and specified the date format for the values of our Map.

The @MapMapping annotation has several methods to configure our Map transformation and the good thing is, as always, we don’t need to provide any implementation. Mapstruct will write that for us.

Invoking Other Mappers:

There can be scenarios where we want to invoke other mappers be it MapStruct generated or hand written to do some work for us, in all such cases we can invoke those external mappers using uses method present in @Mapper annotation. Let’s take an example for better understanding:

Suppose Order.java and OrderDto.java are the nested beans of Customer.java and CustomerDto.java respectively. When generating code for the implementation of the customerToCustomerDto() method, MapStruct will look for a method which has the mapping logic between order and orderDto in OrderMapper.java and will invoke that.

Before Mapping and After Mapping Annotations:

These annotations are used to mark methods that are invoked right before and after the mapping logic.

They are very useful in scenarios where we might want a behavior to be applied to all the mapping methods of our Interface.

Let’s take a look at the below example -

@BeforeMappingMethod marked with this annotation will be invoked as the first statement of each mapping method of our Mapper.

@AfterMappingMethod marked with this annotation will be invoked as the last statement of each mapping method of our Mapper.

Retrieving Mapper and Dependency Injection

Retrieving Mapper Without Dependency Injection:

Before looking at the Spring integration with Mapstruct, let’s have a look at the ways to get the mapper instance.

  • When not using a DI framework, Mapper instances can be retrieved via the org.mapstruct.factory.Mappers class. Just invoke the getMapper() method, passing the interface type of the mapper to return, as shown below:

Retrieving Mapper With Dependency Injection:

MapStruct provides a seamless integration with Dependency Injection for Java Enterprise Application. At present MapStruct supports CDI (Contexts and Dependency Injection for JavaTM EE), JSR330 (Dependency Injection for Java) and the Spring Framework.

It is recommended to obtain mapper objects via dependency injection and not via the Mappers class as described above. To achieve dependency injection of mapper class instance, MapStruct provides a very simple way. All we have to do is specify the DI framework in the componentModel method of @Mapper annotation, let’s see how -

  • If using CDI:
  • If using Spring:

In the above example, you can see we have specified that we want to use Spring as our dependency framework and Dependency Injection should be done using Field.

Just by doing this simple configuration, MapStruct will mark the implementation class as the Component class and Spring will create a bean for it. So, whenever we need to use our mapper, all we have to do is Autowire the Mapper interface and use it.

Bonus Tip : There can be such cases where you need to perform some complex logic while mapping a field like Autowiring some other bean to your Mapper to get some information like active spring profiles by Autowiring Environment bean. In such cases you can’t use Interface as your mapper. Instead, use an Abstract class to declare your mapping methods as abstract methods.

Conclusion

I hope this blog will encourage you to use Mapstruct in your project to save some time which you can invest in doing your favourite activity rather than writing some complicated bean mapping code.

I tried to cover all the frequently used features of Mapstruct but MapStruct has so much more to offer than this. You can always refer to Mapstruct Official Documentation to deep dive into the world of MapStruct.

Happy Coding!

--

--

Akshay Jain
Globant
Writer for

A Technology Geek by passion and Software Developer by profession who loves to capture nature’s beauty through his lens and explore the unexplored in free time.