The Problem
Often times we have a need to map one java object into another one, for example internal entities
(persistent models) into DTO
(Document Transfer Objects) objects.
For this type use cases we mostly use getters and setters to translate data between objects. This seems like tedious when there are so many properties and also a lot of boilerplate code that you need to maintain when there is a new property is added or removed.
We have so many tools to generate code for bean mappings, mapstruct is one of them and lets use it and see how it can eliminate boilerplate code.
See it in Action
Lets add the MapStruct as a dependency in the pom to use this framework.
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>LATEST</version>
<scope>compile</scope>
</dependency>
Now, define the models that we need to translate in between, here is the CustomerSource
model.
public class CustomerSource {
private long id;
private String firstName;
private String lastName;
//getters & setters
}
Here is the CustomerDestination
model.
public class CustomerDestination {
private long id;
private String firstName;
private String lastName;
//getters & setters
}
Now lets define the mapper
to translate between objects.
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);
/**
* Maps the {@link CustomerDestination} into {@link CustomerSource}
*/
CustomerSource translateToSource(CustomerDestination customerDestination);
/**
* Maps the {@link CustomerSource} into {@link CustomerDestination}
*/
CustomerDestination translateToDestination(CustomerSource customerSource);
}
The @Mapper
annotation tells the MapStruct
to generate the code for mapping.
MapStruct
uses annotation processor to generate the code so lets add annotation processor to the maven-compiler-plugin
to generat the code during compile
phase.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>LATEST</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
After this one if you do a maven build, it will generate the mapper
code just like below in target/generated-sources
folder.
package io.tguduru.mapstruct;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-07-01T22:25:19-0500",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_121 (Oracle Corporation)"
)
public class CustomerMapperImpl implements CustomerMapper {
@Override
public CustomerSource translateToSource(CustomerDestination customerDestination) {
if ( customerDestination == null ) {
return null;
}
CustomerSource customerSource = new CustomerSource();
customerSource.setId( customerDestination.getId() );
customerSource.setFirstName( customerDestination.getFirstName() );
customerSource.setLastName( customerDestination.getLastName() );
return customerSource;
}
@Override
public CustomerDestination translateToDestination(CustomerSource customerSource) {
if ( customerSource == null ) {
return null;
}
CustomerDestination customerDestination = new CustomerDestination();
customerDestination.setId( customerSource.getId() );
customerDestination.setFirstName( customerSource.getFirstName() );
customerDestination.setLastName( customerSource.getLastName() );
return customerDestination;
}
}
Now we have all the code we needed to map between CustomerSource
and CustomerDestination
.
Lets see how we can use it. Create a CustomerDestination
object and use the CustomerMapper
to translate into CustomerSource
.
CustomerSource customerSource = new CustomerSource();
customerSource.setId(1l).setFirstName("Bob").setLastName("Smith");
CustomerDestination customerDestination = CustomerMapper.INSTANCE.translateToDestination(customerSource);
With only CustomerMapper
interface we were able to map the java beans.
MapStruct
provides a lot of features which helps us in mapping complex java beans, see the documentation for more details.
Note: Please evaluate the performance of the mapper compared to traditional getters & setters. Getting Mapper
instance through the mapstruct
library is little bit costly for the first time when its loading into jvm so i would suggest to use the generated code instead and do the mapping if its a concern. Im not sure whether this approach works for all the features of mapstruct
or not as i haven’t evaluted them.
Below code snippet show an example of it.
CustomerSource customerSource = new CustomerSource();
customerSource.setId(1l).setFirstName("Bob").setLastName("Smith");
//create an instance of mapper without mapstruct apis
CustomerMapper customerMapper = new CustomerMapperImpl();
CustomerDestination customerDestination = customerMapper.translateToDestination(customerSource);
References
- Example Source Code
- MapStruct Documentation
- Talk
- Aleternative Tools
See also
- Add Custom Validations With Lombok Builders
- Java Code Formatting Maven Plugin
- Create a Docker Image for RESTful Java Service