Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

Creating large Java applications composed of multiple layers require using multiple models such as persistence model, domain model or so-called DTOs. Using multiple models for different application layers will require us to provide a way of mapping between beans.

Doing this manually can quickly create much boilerplate code and consume a lot of time. Luckily for us, there are multiple object mapping frameworks for Java.

In this tutorial, we’re going to compare the performance of the most popular Java mapping frameworks.

2. Mapping Frameworks

2.1. Dozer

Dozer is a mapping framework that uses recursion to copy data from one object to another.  The framework is able not only to copy properties between the beans, but it can also automatically convert between different types.

To use the Dozer framework we need to add such dependency to our project:

<dependency>
    <groupId>com.github.dozermapper</groupId>
    <artifactId>dozer-core</artifactId>
    <version>6.5.2</version>
</dependency>

More information about the usage of the Dozer framework can be found in this article.

The documentation of the framework can be found here, and the latest version can be found here.

2.2. Orika

Orika is a bean to bean mapping framework that recursively copies data from one object to another.

The general principle of work of the Orika is similar to Dozer. The main difference between the two is the fact that Orika uses bytecode generation. This allows for generating faster mappers with minimal overhead.

To use it, we need to add such dependency to our project:

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.4</version>
</dependency>

More detailed information about the usage of the Orika can be found in this article.

The actual documentation of the framework can be found here, and the latest version can be found here.

Warning: Since Java 16, illegal reflective accesses are denied by default. The 1.5.4 version of Orika uses such reflective accesses, so Orika is currently not usable in combination with Java 16. This problem will alledgedly be solved in the future with the release of the 1.6.0 version.

2.3. MapStruct

MapStruct is a code generator that generates bean mapper classes automatically.

MapStruct also has the ability to convert between different data types. More information on how to use it can be found in this article.

To add MapStruct to our project we need to include the following dependency :

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.6.0.Beta1</version>
</dependency>

The documentation of the framework can be found here, and the latest version can be found here.

2.4. ModelMapper

ModelMapper is a framework that aims to simplify object mapping, by determining how objects map to each other based on conventions. It provides type-safe and refactoring-safe API.

More information about the framework can be found in the documentation.

To include ModelMapper in our project we need to add the following dependency:

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>3.2.0</version>
</dependency>

The latest version of the framework can be found here.

2.5. JMapper

JMapper is the mapping framework that aims to provide an easy-to-use, high-performance mapping between Java Beans.

The framework aims to apply the DRY principle using Annotations and relational mapping.

The framework allows for different ways of configuration: annotation-based, XML or API-based.

More information about the framework can be found in its documentation.

To include JMapper in our project we need to add its dependency:

<dependency>
    <groupId>com.googlecode.jmapper-framework</groupId>
    <artifactId>jmapper-core</artifactId>
    <version>1.6.1.CR2</version>
</dependency>

The latest version of the framework can be found here.

3. Testing Model

To be able to test mapping properly we need to have source and target models. We’ve created two testing models.

First one is just a simple POJO with one String field, this allowed us to compare frameworks in simpler cases and check whether anything changes if we use more complicated beans.

The simple source model looks like below:

public class SourceCode {
    String code;
    // getter and setter
}

And its destination is quite similar:

public class DestinationCode {
    String code;
    // getter and setter
}

The real-life example of source bean looks like that:

public class SourceOrder {
    private String orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private DeliveryData deliveryData;
    private User orderingUser;
    private List<Product> orderedProducts;
    private Shop offeringShop;
    private int orderId;
    private OrderStatus status;
    private LocalDate orderDate;
    // standard getters and setters
}

And the target class looks like below:

public class Order {
    private User orderingUser;
    private List<Product> orderedProducts;
    private OrderStatus orderStatus;
    private LocalDate orderDate;
    private LocalDate orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private int shopId;
    private DeliveryData deliveryData;
    private Shop offeringShop;
    // standard getters and setters
}

The whole model structure can be found here.

4. Converters

To simplify the design of the testing setup, we’ve created the Converter interface:

public interface Converter {
    Order convert(SourceOrder sourceOrder);
    DestinationCode convert(SourceCode sourceCode);
}

And all our custom mappers will implement this interface.

4.1. OrikaConverter

Orika allows for full API implementation, this greatly simplifies the creation of the mapper:

public class OrikaConverter implements Converter{
    private MapperFacade mapperFacade;

    public OrikaConverter() {
        MapperFactory mapperFactory = new DefaultMapperFactory
          .Builder().build();

        mapperFactory.classMap(Order.class, SourceOrder.class)
          .field("orderStatus", "status").byDefault().register();
        mapperFacade = mapperFactory.getMapperFacade();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapperFacade.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapperFacade.map(sourceCode, DestinationCode.class);
    }
}

4.2. DozerConverter

Dozer requires XML mapping file,  with the following sections:

<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
  https://dozermapper.github.io/schema/bean-mapping.xsd">

    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceOrder</class-a>
        <class-b>com.baeldung.performancetests.model.destination.Order</class-b>
        <field>
            <a>status</a>
            <b>orderStatus</b>
        </field>
    </mapping>
    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceCode</class-a>
        <class-b>com.baeldung.performancetests.model.destination.DestinationCode</class-b>
    </mapping>
</mappings>

After defining the XML mapping, we can use it from code:

public class DozerConverter implements Converter {
    private final Mapper mapper;

    public DozerConverter() {
        this.mapper = DozerBeanMapperBuilder.create()
          .withMappingFiles("dozer-mapping.xml")
          .build();       
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapper.map(sourceOrder,Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapper.map(sourceCode, DestinationCode.class);
    }
}

4.3. MapStructConverter

MapStruct definition is quite simple as it’s entirely based on code generation:

@Mapper
public interface MapStructConverter extends Converter {
    MapStructConverter MAPPER = Mappers.getMapper(MapStructConverter.class);

    @Mapping(source = "status", target = "orderStatus")
    @Override
    Order convert(SourceOrder sourceOrder);

    @Override
    DestinationCode convert(SourceCode sourceCode);
}

4.4. JMapperConverter

JMapperConverter requires more work to do. After implementing the interface:

public class JMapperConverter implements Converter {
    JMapper realLifeMapper;
    JMapper simpleMapper;
 
    public JMapperConverter() {
        JMapperAPI api = new JMapperAPI()
          .add(JMapperAPI.mappedClass(Order.class));
        realLifeMapper = new JMapper(Order.class, SourceOrder.class, api);
        JMapperAPI simpleApi = new JMapperAPI()
          .add(JMapperAPI.mappedClass(DestinationCode.class));
        simpleMapper = new JMapper(
          DestinationCode.class, SourceCode.class, simpleApi);
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return (Order) realLifeMapper.getDestination(sourceOrder);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return (DestinationCode) simpleMapper.getDestination(sourceCode);
    }
}

We also need to add @JMap annotations to each field of the target class. Also, JMapper can’t convert between enum types on its own and it requires us to create custom mapping functions :

@JMapConversion(from = "paymentType", to = "paymentType")
public PaymentType conversion(com.baeldung.performancetests.model.source.PaymentType type) {
    PaymentType paymentType = null;
    switch(type) {
        case CARD:
            paymentType = PaymentType.CARD;
            break;

        case CASH:
            paymentType = PaymentType.CASH;
            break;

        case TRANSFER:
            paymentType = PaymentType.TRANSFER;
            break;
    }
    return paymentType;
}

4.5. ModelMapperConverter

ModelMapperConverter requires us to only provide the classes that we want to map:

public class ModelMapperConverter implements Converter {
    private ModelMapper modelMapper;

    public ModelMapperConverter() {
        modelMapper = new ModelMapper();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
       return modelMapper.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return modelMapper.map(sourceCode, DestinationCode.class);
    }
}

5. Simple Model Testing

For the performance testing, we can use Java Microbenchmark Harness, more information about how to use it can be found in this article.

We’ve created a separate benchmark for each Converter with specifying BenchmarkMode to Mode.All.

5.1. AverageTime

JMH returned the following results for average running time (the lesser the better) :

Framework Name Average running time (in ms per operation)
MapStruct 10 -5
JMapper 10 -5
Orika 0.001
ModelMapper 0.002
Dozer 0.004

 

This benchmark shows clearly that both MapStruct and JMapper have the best average working times.

5.2. Throughput

In this mode, the benchmark returns the number of operations per second. We have received the following results (more is better) :

Framework Name Throughput (in operations per ms)
MapStruct 58101
JMapper 53667
Orika 1195
ModelMapper 379
Dozer 230

 

In throughput mode, MapStruct was the fastest of the tested frameworks, with JMapper a close second.

5.3. SingleShotTime

This mode allows measuring the time of single operation from it’s beginning to the end. The benchmark gave the following result (less is better):

Framework Name Single Shot Time (in ms per operation)
JMapper 0.016
MapStruct 1.904
Dozer 3.864
Orika 6.593
ModelMapper 8.788

 

Here, we see that JMapper returns better result than MapStruct.

5.4. SampleTime

This mode allows sampling of the time of each operation.  The results for three different percentiles  look like below:

Sample Time (in milliseconds per operation)
Framework Name p0.90 p0.999 p1.0
JMapper 10 -4 0.001 1.526
MapStruct 10 -4 10 -4 1.948
Orika 0.001 0.018 2.327
ModelMapper 0.002 0.044 3.604
Dozer 0.003 0.088 5.382

 

All benchmarks have shown that MapStruct and JMapper are both good choices depending on the scenario.

6. Real-Life Model Testing

For the performance testing, we can use Java Microbenchmark Harness, more information about how to use it can be found in this article.

We have created a separate benchmark for each Converter with specifying BenchmarkMode to Mode.All.

6.1. AverageTime

JMH returned the following results for average running time (less is better) :

Framework Name Average running time (in ms per operation)
MapStruct 10 -4
JMapper 10 -4
Orika 0.007
ModelMapper 0.137
Dozer 0.145

6.2. Throughput

In this mode, the benchmark returns the number of operations per second. For each of the mappers we’ve received the following results (more is better) :

Framework Name Throughput (in operations per ms)
JMapper 3205
MapStruct 3467
Orika 121
ModelMapper 7
Dozer 6.342

6.3. SingleShotTime

This mode allows measuring the time of single operation from it’s beginning to the end. The benchmark gave the following results (less is better):

Framework Name Single Shot Time (in ms per operation)
JMapper 0.722
MapStruct 2.111
Dozer 16.311
ModelMapper 22.342
Orika 32.473

6.4. SampleTime

This mode allows sampling of the time of each operation. Sampling results are split into percentiles, we’ll present results for three different percentiles p0.90, p0.999, and p1.00:

Sample Time (in milliseconds per operation)
Framework Name p0.90 p0.999 p1.0
JMapper 10 -3 0.006 3
MapStruct 10 -3 0.006 8
Orika 0.007 0.143 14
ModelMapper 0.138 0.991 15
Dozer 0.131 0.954 7

While the exact results of the simple example and the real-life example were clearly different, but they do follow more or less the same trend. In both examples, we saw a close contest between JMapper and MapStruct for the top spot.

6.5. Conclusion

Based on the real-life model testing we performed in this section, we can see that the best performance clearly belongs to JMapper, although MapStruct is a close second. In the same tests, we see that Dozer is consistently at the bottom of our results table, except for SingleShotTime.

7. Summary

In this article, we’ve conducted performance tests of five popular Java bean mapping frameworks: ModelMapper, MapStruct, Orika, Dozer, and JMapper.

As always, code samples can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.