Java Bean Mappings Using MapStruct


Categories: java

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

See also

comments powered by Disqus