Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Performance Benchmarking of Five Java Bean Mapping Frameworks

Tech 1

Layered Java applications typically use multiple object models (JPA entities, domain objects, DTOs). Moving data across these layers requires object-to-object mapping. Hand-written mappers scale poorly and are error prone, so many teams adopt mapping libraries. This document compares five popular options from a performance perspective and shows minimal setup for each.

Mapping libraries

Dozer

Dozer performs recursive, reflective property copying and supports type conversions. Configuration commonly lives in XML.

Maven dependency:

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.5.1</version>
</dependency>

Orika

Orika builds mappers that generate bytecode at runtime, reducing reflection overhead compared to pure reflection-based tools.

Maven dependency:

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

MapStruct

MapStruct is an annotation-driven code generator that produces mapping classes at compile time, avoiding reflection entirely while supporting custom conversions.

Maven dependencies (processor + API):

<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.2.0.Final</version>
</dependency>
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct-processor</artifactId>
  <version>1.2.0.Final</version>
  <scope>provided</scope>
</dependency>

ModelMapper

ModelMapper focuses on convention-based mapping, with a type-safe API and reflection under the hood. It aims for minimal configuration for typical cases.

Maven dependency:

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

JMapper

JMapper targets high-throughput bean mapping with a configuration model based on annotations, XML, or API definitions.

Maven dependency:

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

Test models

Two models are used to probe both trivial and realistic scenarios.

Simple source and target:

public class SourceCode {
    private String code;
    // getters/setters
}
public class DestinationCode {
    private String code;
    // getters/setters
}

Richer source and destination objects:

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;
    // getters/setters
}
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;
    // getters/setters
}

Converter abstraction

A minimal interface keeps the benchmark harness uniform across libraries.

public interface BeanTransformer {
    Order toOrder(SourceOrder from);
    DestinationCode toCode(DestinationCode from); // intentionally incorrect signature to demonstrate change? Adjust: Must be SourceCode to DestinationCode
}

Correction: Use the intended signatures.

public interface BeanTransformer {
    Order toOrder(SourceOrder from);
    DestinationCode toCode(SourceCode from);
}

Library-specific implementations

Orika implementation

Orika offers a fluent API to declare mappings. The example registers a property rename from status to orderStatus and relies on byDefault for the rest.

public class OrikaTransformer implements BeanTransformer {
    private final MapperFacade mapper;

    public OrikaTransformer() {
        MapperFactory factory = new DefaultMapperFactory.Builder().build();
        factory.classMap(SourceOrder.class, Order.class)
               .field("status", "orderStatus")
               .byDefault()
               .register();
        factory.classMap(SourceCode.class, DestinationCode.class).byDefault().register();
        this.mapper = factory.getMapperFacade();
    }

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

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

Dozer implementation

Dozer typically reads mappings from XML. A minimal configuration might look like this:

dozer-bean-mappings.xml

<mappings xmlns="http://dozer.sourceforge.net"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/beanmapping.xsd">
    <mapping>
        <class-a>com.example.SourceOrder</class-a>
        <class-b>com.example.Order</class-b>
        <field>
            <a>status</a>
            <b>orderStatus</b>
        </field>
    </mapping>

    <mapping>
        <class-a>com.example.SourceCode</class-a>
        <class-b>com.example.DestinationCode</class-b>
    </mapping>
</mappings>

Java wrapper:

public class DozerTransformer implements BeanTransformer {
    private final Mapper beanMapper;

    public DozerTransformer() {
        DozerBeanMapper m = new DozerBeanMapper();
        m.addMapping(DozerTransformer.class.getResourceAsStream("/dozer-bean-mappings.xml"));
        this.beanMapper = m;
    }

    @Override
    public Order toOrder(SourceOrder from) {
        return beanMapper.map(from, Order.class);
    }

    @Override
    public DestinationCode toCode(SourceCode from) {
        return beanMapper.map(from, DestinationCode.class);
    }
}

MapStruct implementation

MapStruct generates the implementation at compile time based on annotations.

@Mapper
public interface MapStructTransformer extends BeanTransformer {
    MapStructTransformer INSTANCE = Mappers.getMapper(MapStructTransformer.class);

    @Override
    @Mapping(source = "status", target = "orderStatus")
    Order toOrder(SourceOrder from);

    @Override
    DestinationCode toCode(SourceCode from);
}

JMapper implementation

JMapper can be configured via API. Fields in the destination can also be annotated with @JMap. Some enum conversions may require explicit converters.

public class JMapperTransformer implements BeanTransformer {
    private final JMapper<Order, SourceOrder> orderMapper;
    private final JMapper<DestinationCode, SourceCode> codeMapper;

    public JMapperTransformer() {
        JMapperAPI orderApi = new JMapperAPI()
            .add(JMapperAPI.mappedClass(Order.class));
        this.orderMapper = new JMapper<>(Order.class, SourceOrder.class, orderApi);

        JMapperAPI codeApi = new JMapperAPI()
            .add(JMapperAPI.mappedClass(DestinationCode.class));
        this.codeMapper = new JMapper<>(DestinationCode.class, SourceCode.class, codeApi);
    }

    @Override
    public Order toOrder(SourceOrder from) {
        return orderMapper.getDestination(from);
    }

    @Override
    public DestinationCode toCode(SourceCode from) {
        return codeMapper.getDestination(from);
    }
}

Example of a custom enum conversion method when needed:

@JMapConversion(from = "paymentType", to = "paymentType")
public PaymentType toDestPaymentType(com.example.source.PaymentType src) {
    switch (src) {
        case CARD:     return PaymentType.CARD;
        case CASH:     return PaymentType.CASH;
        case TRANSFER: return PaymentType.TRANSFER;
        default:       return null;
    }
}

ModelMapper implementation

ModelMapper relies on convention for most mappings.

public class ModelMapperTransformer implements BeanTransformer {
    private final ModelMapper mm = new ModelMapper();

    @Override
    public Order toOrder(SourceOrder from) {
        return mm.map(from, Order.class);
    }

    @Override
    public DestinationCode toCode(SourceCode from) {
        return mm.map(from, DestinationCode.class);
    }
}

Benchmark setup

Microbenchmarks were written using JMH. Each library has its own benchmark method, and all modes below were executed:

  • AverageTime: mean execution time per operation
  • Throughput: operations per second
  • SingleShotTime: cold, single-invocation timing
  • SampleTime: sampled latency distribution (selected percentiles)

A minimal JMH scaffold for the simple mapping case:

@State(Scope.Thread)
public class SimpleMapState {
    SourceCode input;
    OrikaTransformer orika = new OrikaTransformer();
    DozerTransformer dozer = new DozerTransformer();
    MapStructTransformer mapstruct = MapStructTransformer.INSTANCE;
    ModelMapperTransformer modelMapper = new ModelMapperTransformer();
    JMapperTransformer jmapper = new JMapperTransformer();

    @Setup(Level.Trial)
    public void init() {
        input = new SourceCode();
        input.setCode("abc-123");
    }
}

public class SimpleBench {
    @Benchmark public DestinationCode orika(SimpleMapState s) { return s.orika.toCode(s.input); }
    @Benchmark public DestinationCode dozer(SimpleMapState s) { return s.dozer.toCode(s.input); }
    @Benchmark public DestinationCode mapstruct(SimpleMapState s) { return s.mapstruct.toCode(s.input); }
    @Benchmark public DestinationCode modelmapper(SimpleMapState s) { return s.modelMapper.toCode(s.input); }
    @Benchmark public DestinationCode jmapper(SimpleMapState s) { return s.jmapper.toCode(s.input); }
}

A similar fixture is used for the richer Order mapping.

Results overview

Simple model:

  • AverageTime: MapStruct and JMapper produced the lowest mean latencies.
  • Throughput: MapStruct led, JMapper closely followed.
  • SingleShotTime: JMapper showed the strongest cold-start numbers; MapStruct lagged in this mode due to initialization effects in some setups.
  • SampleTime: Percentile latencies confirmed the above trend; MapStruct and JMapper clustered tightly at low latencies.

Realistic model:

  • AverageTime: MapStruct and JMapper again ranked at the top.
  • Throughput: MapStruct achieved the highest ops/s, followed by JMapper.
  • SingleShotTime: Results favored JMapper on first-invocation timing.
  • SampleTime: Distribution patterns were consistent with the simple case, though absolute values were higher due to object complexity.

Observed ordering across both scenarios remained stable: MapStruct and JMapper performed best overall, Orika and ModelMapper trailed in the middle depending on mode, and Dozer was consistently slowest in these measruements.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.