I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

1. Overview

In this article, we’ll explore the use of MapStruct which is, simply put, a Java Bean mapper.

This API contains functions that automatically map between two Java Beans. With MapStruct we only need to create the interface, and the library will automatically create a concrete implementation during compile time.

2. MapStruct and Transfer Object Pattern

For most applications, you’ll notice a lot of boilerplate code converting POJOs to other POJOs.

For example, a common type of conversion happens between persistence-backed entities and DTOs that go out to the client side.

So that is the problem that MapStruct solvesmanually creating bean mappers is time-consuming. The library can generate bean mapper classes automatically.

3. Maven

Let’s add the below dependency into our Maven pom.xml:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.1.0.Final</version> 
</dependency>

The latest stable release of Mapstruct and his processor are both available from the Maven Central Repository.

Let’s also add the annotationProcessorPaths section to the configuration part of the maven-compiler-plugin plugin.

The mapstruct-processor is used to generate the mapper implementation during the build:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>1.8</source>
    <target>1.8</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.1.0.Final</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

4. Basic Mapping

4.1. Creating a POJO

Let’s first create a simple Java POJO:

public class SimpleSource {
    private String name;
    private String description;
    // getters and setters
}
 
public class SimpleDestination {
    private String name;
    private String description;
    // getters and setters
}

4.2. The Mapper Interface

@Mapper
public interface SimpleSourceDestinationMapper {
    SimpleDestination sourceToDestination(SimpleSource source);
    SimpleSource destinationToSource(SimpleDestination destination);
}

Notice we did not create an implementation class for our SimpleSourceDestinationMapper – because MapStruct creates it for us.

4.3. The New Mapper

We can trigger the MapStruct processing by executing an mvn clean install.

This will generate the implementation class under /target/generated-sources/annotations/.

Here is the class that MapStruct auto-creates for us:

public class SimpleSourceDestinationMapperImpl
  implements SimpleSourceDestinationMapper {
    @Override
    public SimpleDestination sourceToDestination(SimpleSource source) {
        if ( source == null ) {
            return null;
        }
        SimpleDestination simpleDestination = new SimpleDestination();
        simpleDestination.setName( source.getName() );
        simpleDestination.setDescription( source.getDescription() );
        return simpleDestination;
    }
    @Override
    public SimpleSource destinationToSource(SimpleDestination destination){
        if ( destination == null ) {
            return null;
        }
        SimpleSource simpleSource = new SimpleSource();
        simpleSource.setName( destination.getName() );
        simpleSource.setDescription( destination.getDescription() );
        return simpleSource;
    }
}

4.4. A Test Case

Finally, with everything generated, let’s write a test case will show that values in SimpleSource match values in SimpleDestination.

public class SimpleSourceDestinationMapperTest {
    private SimpleSourceDestinationMapper mapper
      = Mappers.getMapper(SimpleSourceDestinationMapper.class);
    @Test
    public void givenSourceToDestination_whenMaps_thenCorrect() {
        SimpleSource simpleSource = new SimpleSource();
        simpleSource.setName("SourceName");
        simpleSource.setDescription("SourceDescription");
        SimpleDestination destination = mapper.sourceToDestination(simpleSource);
 
        assertEquals(simpleSource.getName(), destination.getName());
        assertEquals(simpleSource.getDescription(), 
          destination.getDescription());
    }
    @Test
    public void givenDestinationToSource_whenMaps_thenCorrect() {
        SimpleDestination destination = new SimpleDestination();
        destination.setName("DestinationName");
        destination.setDescription("DestinationDescription");
        SimpleSource source = mapper.destinationToSource(destination);
        assertEquals(destination.getName(), source.getName());
        assertEquals(destination.getDescription(),
          source.getDescription());
    }
}

5. Mapping with Dependency Injection

Next, let’s obtain an instance of a mapper in MapStruct by merely calling Mappers.getMapper(YourClass.class).

Of course, that’s a very manual way of getting the instance – a much better alternative would be injecting the mapper directly where we need it (if our project uses any Dependency Injection solution).

Luckily MapStruct has solid support for both Spring and CDI (Contexts and Dependency Injection).

To use Spring IoC in our mapper, we need to add the componentModelattribute to @Mapper with the value spring and for CDI would be cdi .

5.1. Modify the Mapper

Add the following code to SimpleSourceDestinationMapper:

@Mapper(componentModel = "spring")
public interface SimpleSourceDestinationMapper

6. Mapping Fields with Different Field Names

From our previous example, MapStruct was able to map our beans automatically because they have the same field names. So what if a bean we are about to map has a different field name?

For our example, we will be creating a new bean called Employee and EmployeeDTO.

6.1. New POJOs

public class EmployeeDTO {
    private int employeeId;
    private String employeeName;
    // getters and setters
}
public class Employee {
    private int id;
    private String name;
    // getters and setters
}

6.2. The Mapper Interface

When mapping different field names, we will need to configure its source field to its target field and to do that we will need to add @Mappings annotation. This annotation accepts an array of @Mapping annotation which we will use to add the target and source attribute.

In MapStruct we can also use dot notation to define a member of a bean:

@Mapper
public interface EmployeeMapper {
    @Mappings({
      @Mapping(target="employeeId", source="entity.id"),
      @Mapping(target="employeeName", source="entity.name")
    })
    EmployeeDTO employeeToEmployeeDTO(Employee entity);
    @Mappings({
      @Mapping(target="id", source="dto.employeeId"),
      @Mapping(target="name", source="dto.employeeName")
    })
    Employee employeeDTOtoEmployee(EmployeeDTO dto);
}

6.3. The Test Case

Again we need to test that both source and destination object values match:

@Mapper
public interface EmployeeMapper {
    @Mappings({
      @Mapping(target="employeeId", source="entity.id"),
      @Mapping(target="employeeName", source="entity.name")
    })
    EmployeeDTO employeeToEmployeeDTO(Employee entity);
    @Mappings({
      @Mapping(target="id", source="dto.employeeId"),
      @Mapping(target="name", source="dto.employeeName")
    })
    Employee employeeDTOtoEmployee(EmployeeDTO dto);
}

More test cases can be found in the Github project.

7. Mapping Beans with Child Beans

Next, we’ll show how to map a bean with references to other beans.

7.1. Modify the POJO

Let’s add a new bean reference in the Employee object:

public class EmployeeDTO {
    private int employeeId;
    private String employeeName;
    private DivisionDTO division;
    // getters and setters omitted
}
public class Employee {
    private int id;
    private String name;
    private Division division;
    // getters and setters omitted
}
public class Division {
    private int id;
    private String name;
    // default constructor, getters and setters omitted
}

7.2. Modify the Mapper

Here we need to add a method to convert the Division to DivisionDTO and vice versa; if MapStruct detects that the object type needs to be converted and the method to convert exists in the same class, then it will use it automatically.

Let’s add this to the mapper:

DivisionDTO divisionToDivisionDTO(Division entity);

Division divisionDTOtoDivision(DivisionDTO dto);

7.3. Modify the Test Case

Let’s modify and add a few test cases to the existing one:

@Test
public void givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect() {
    EmployeeDTO dto = new EmployeeDTO();
    dto.setDivision(new DivisionDTO(1, "Division1"));
    Employee entity = mapper.employeeDTOtoEmployee(dto);
    assertEquals(dto.getDivision().getId(), 
      entity.getDivision().getId());
    assertEquals(dto.getDivision().getName(), 
      entity.getDivision().getName());
}

8. Mapping With Type Conversion

MapStruct also offers a couple of ready-made implicit type conversions, and for our example, we will try to convert a String date to an actual Date object.

For more details on implicit type conversion, you may read the MapStruct reference guide.

8.1. Modify the Beans

Add a start date for our employee:

public class Employee {
    // other fields
    private Date startDt;
    // getters and setters
}
public class EmployeeDTO {
    // other fields
    private String employeeStartDt;
    // getters and setters
}

8.2. Modify the Mapper

Modify the mapper and provide the dateFormat for our start date:

@Mappings({
  @Mapping(target="employeeId", source = "entity.id"),
  @Mapping(target="employeeName", source = "entity.name"),
  @Mapping(target="employeeStartDt", source = "entity.startDt",
           dateFormat = "dd-MM-yyyy HH:mm:ss")})
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mappings({
  @Mapping(target="id", source="dto.employeeId"),
  @Mapping(target="name", source="dto.employeeName"),
  @Mapping(target="startDt", source="dto.employeeStartDt",
           dateFormat="dd-MM-yyyy HH:mm:ss")})
Employee employeeDTOtoEmployee(EmployeeDTO dto);

8.3. Modify the Test Case

Let’s add a few more test case to verify the conversion is correct:

private static final String DATE_FORMAT = "dd-MM-yyyy HH:mm:ss";
@Test
public void givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect() throws ParseException {
    Employee entity = new Employee();
    entity.setStartDt(new Date());
    EmployeeDTO dto = mapper.employeeToEmployeeDTO(entity);
    SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
 
    assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
      entity.getStartDt().toString());
}
@Test
public void givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect() throws ParseException {
    EmployeeDTO dto = new EmployeeDTO();
    dto.setEmployeeStartDt("01-04-2016 01:00:00");
    Employee entity = mapper.employeeDTOtoEmployee(dto);
    SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
 
    assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
      entity.getStartDt().toString());
}

9. Mapping With an Abstract Class

Sometimes, we may want to customize our mapper in a way which exceeds @Mapping capabilities.

For example, in addition to type conversion, we may want to transform the values in some way as in our example below.

In such case, we can create an abstract class and implement methods we want to have customized and leave abstract those, that should be generated by MapStruct.

9.1. Basic Model

In this example, we’ll use the following class:

public class Transaction {
    private Long id;
    private String uuid = UUID.randomUUID().toString();
    private BigDecimal total;

    //standard getters
}

and a matching DTO:

public class TransactionDTO {

    private String uuid;
    private Long totalInCents;

    // standard getters and setters
}

The tricky part here is converting the BigDecimal totalamount of dollars into a Long totalInCents.

9.2. Defining a Mapper

We can achieve this by creating our Mapper as an abstract class:

@Mapper
abstract class TransactionMapper {

    public TransactionDTO toTransactionDTO(Transaction transaction) {
        TransactionDTO transactionDTO = new TransactionDTO();
        transactionDTO.setUuid(transaction.getUuid());
        transactionDTO.setTotalInCents(transaction.getTotal()
          .multiply(new BigDecimal("100")).longValue());
        return transactionDTO;
    }

    public abstract List<TransactionDTO> toTransactionDTO(Collection<Transaction> transactions);
}

Here, we’ve implemented our fully customized mapping method for a single object conversion.

On the other hand, we left the method which is meant to map Collectionto a Listabstract, so MapStruct will implement it for us.

9.3. Generated Result

As we have already implemented the method to map single Transactioninto a TransactionDTO, we expect Mapstructto use it in the second method. The following will be generated:

@Generated
class TransactionMapperImpl extends TransactionMapper {

    @Override
    public List<TransactionDTO> toTransactionDTO(Collection<Transaction> transactions) {
        if ( transactions == null ) {
            return null;
        }

        List<TransactionDTO> list = new ArrayList<>();
        for ( Transaction transaction : transactions ) {
            list.add( toTransactionDTO( transaction ) );
        }

        return list;
    }
}

As we can see in line 12, MapStruct uses our implementation in the method, that it generated.

 

10. Conclusion

This article provided an introduction to MapStruct, we have introduced most of the basics of the Mapping library and how to use it in our applications.

The implementation of these examples and tests can be found in the Github project. This is a Maven project, so it should be easy to import and run as it is.

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE LESSONS

newest oldest most voted
Ramesh Babu Y
Guest
Ramesh Babu Y

thanks for inroducing the new lib , very useful in mapping , and may i know how advacned this when compared with BeanUtil related classed and when to use which ,, can you give some idea please .

Grzegorz Piwowarek
Guest
Grzegorz Piwowarek

BeanUtils can be used for simple properties copying from one class to the other using Reflection. However, MapStruct actually generates mapper classes at compile-time which makes mappings easily testable and debuggable. No Reflection magic involved 🙂

Ramesh Babu Y
Guest
Ramesh Babu Y

HI Grzegorz Piwowarek , Thanks for the response , so in that case MapStruct is more efficient , can i use this MapStruct in all the places wherever i used the BeanUtils ?

Grzegorz Piwowarek
Guest
Grzegorz Piwowarek

Ramesh, I would not say it’s more efficient but definitely the biggest advantage is that you can actually inspect what is actually going on in generated classes. Debugging BeanUtils is a nightmare 🙂
I am not a MapStruct expert but I do not see any reasons why it would not be possible.

Gunnar Morling
Guest
Gunnar Morling

Hi, original Author of MapStruct here 🙂 MapStruct is definitely more efficient than any reflection-based approach. Plain method invocations done in the generated code is as fast as it gets. Huge runtime overhead of a reflection-based mapping tool (not BeanUtils, another one) was one of the motivators for writing it. Also you get quick feedback right in your IDE on wrong or incomplete mappings (try our Eclipse plug-in for some more goodies such as code completion in @Mapping annotations). Plus, the generated code has no runtime dependencies, which e.g. makes it nice for Android and similar embedded usage.

Eugen Paraschiv
Guest

Hey Gunnar, thanks for the quick feedback here.
It would be interesting to maybe set up a reference benchmark if one doesn’t already exist. There are a few good ones floating around Github for JSON libraries, but I haven’t seen one focused on these mapping libraries. Do you know if a benchmark like that exists?
Cheers,
Eugen.

Gunnar Morling
Guest
Gunnar Morling

One comparison I’m aware of is this one: https://github.com/jbreis/compare-java-converters. Haven’t looked into it for a while, though. Numbers for MapStruct are essentially the same as for hand-written mapping code: https://twitter.com/joao_b_reis/status/559780053979250688

Eugen Paraschiv
Guest

Cool – it looks interesting. I’m adding the topic to the content calendar of the site – maybe we can use that test and get some updated numbers. Thanks for pointing it out.
Cheers,
Eugen.

Chris Contreras
Guest
Chris Contreras

Thanks for posting this kind of articles @baeldung:disqus , you are one of the best sources to be updated. Keep it up!

Eugen Paraschiv
Guest

Glad you’re enjoying the site Chris. Cheers,
Eugen.