Bean Mapping: Alternatives to ModelMapper

Exploring Additional Frameworks for Bean Mapping

I often write about ModelMapper and they are some of my most popular posts.  That got me wondering what other bean mapping frameworks are out there and what they had to offer.  In this post, I’m going to make a quick pass, that includes a quick example of usage, over four other bean mapping frameworks.

The Beans

Before we get in to the frameworks, let’s take a look at the beans we’ll be mapping to and from.  We’re sticking with the task list example we’ve been using recently.

Here’s our Task entity:

@Entity
public class Task {
   
   @Id
   @GeneratedValue(strategy=GenerationType.AUTO)
   private Long id;
   private String description;
   private LocalDate startDate;
   private LocalDate completionDate;
   private Priorities priority;
   private Statuses status;

   // Getters and setters
}

And here’s our TaskDto:

public class TaskDto {
   private Long id;
   @NotNull
   private String description;
   private LocalDate startDate;
   private LocalDate completionDate;
   private String priority;
   private String status;

   // Getters and setters
}

JMapper

The first one we’ll look at is JMapper.  JMapper tries to be fast and easy to use.  There are three options for configuration: annotations, XML and API.

At first I was intrigued by the annotations.  I’ve come to really appreciate annotations for JPA and validation not to mention their use throughout Spring.  But when it came time to implement a custom converter for the priority and status fields, I was ultimately too uncomfortable putting the conversion code in the Entity and DTO classes.  My preference is to avoid XML configuration as much as possible, so I ended up taking the API route.

With that said, let’s take a look at our example.

Gradle Dependency

implementation 'com.googlecode.jmapper-framework:jmapper-core:1.6.0.1'

TaskJMapperController Setup

When using the JMapper API, there’s quite a bit of configuration for setting up the required mappers.  We have two mappers: one for outgoing mapping Tasks to TaskDtos and another for the incoming side of thing that maps TaskDtos to Tasks.

In the constructor, we configure each API.  We use mappedClass to point to our TaskDto class.  We can add fields individually, but since we want to map all the fields, we use global() for that.

JMapper didn’t gracefully handle the conversion from our Enum fields in our Task entity to our String fields in our DTO, so we need to add custom converters for that.  So we add a conversion for both priority and status.  In the body, we actually write the Java code that converts.  ${source} represents the source value.

We do similar configuration for the incoming API.

Finally, we create our mappers providing our destination and source classes and the APIs we just created.

private JMapper<TaskDto, Task> outgoingMapper;
private JMapper<Task, TaskDto> incomingMapper;

public TaskJMapperController() {
   JMapperAPI outgoingAPI = new JMapperAPI();
   outgoingAPI.add(mappedClass(TaskDto.class).add(global())
      .add(conversion("priority")
         .from("priority").to("priority")
         .type(JMapConversion.Type.DYNAMIC)
         .body("if (${source} == com.amydegregorio.mappers.util.Priorities.HIGH) {"
             + "return \"HIGH\"; } "
             + "else if (${source} == com.amydegregorio.mappers.util.Priorities.MEDIUM) {"
             + "return \"MEDIUM\"; }"
             + "else if (${source} == com.amydegregorio.mappers.util.Priorities.LOW) {"
             + "return \"LOW\"; }"
             + "else if (${source} == com.amydegregorio.mappers.util.Priorities.URGENT) {"
             + "return \"URGENT\"; }"
             +"else return \"\";"))
       .add(conversion("status")
          .from("status").to("status")
          .type(JMapConversion.Type.DYNAMIC)
          .body("if (${source} == com.amydegregorio.mappers.util.Statuses.NOT_STARTED) {" 
             + "return \"NOT_STARTED\";}"
             + "else if (${source} == com.amydegregorio.mappers.util.Statuses.IN_PROGRESS) { "
             + "return \"IN_PROGRESS\";}"
             + "else if (${source} == com.amydegregorio.mappers.util.Statuses.COMPLETE) {"
             + "return \"COMPLETE\";} "
             + "return \"\";"))
      );
      
   JMapperAPI incomingAPI = new JMapperAPI();
   incomingAPI.add(mappedClass(Task.class).add(global())
      .add(conversion("priority")
         .from("priority").to("priority")
         .type(JMapConversion.Type.DYNAMIC)
         .body("if (${source}.equals(\"HIGH\")) {" 
            + "return com.amydegregorio.mappers.util.Priorities.HIGH;}"
            + "else if(${source}.equals(\"MEDIUM\")) {"
            + "return com.amydegregorio.mappers.util.Priorities.MEDIUM;}"
            + "else if(${source}.equals(\"LOW\")) {"
            + "return com.amydegregorio.mappers.util.Priorities.LOW;}"
            + "else if(${source}.equals(\"URGENT\")) {"
            + "return com.amydegregorio.mappers.util.Priorities.URGENT;}"
            + "else { return null;}"))
      .add(conversion("status")
         .from("status").to("status")
         .type(JMapConversion.Type.DYNAMIC)
         .body("if (${source}.equals(\"NOT_STARTED\")) {"
            + "return com.amydegregorio.mappers.util.Statuses.NOT_STARTED;}"
            + "else if(${source}.equals(\"IN_PROGRESS\")) {"
            + "return com.amydegregorio.mappers.util.Statuses.IN_PROGRESS;}"
            + "else if(${source}.equals(\"COMPLETE\")) {"
            + "return com.amydegregorio.mappers.util.Statuses.COMPLETE;}"
            + "else { return null;}"))
      );
      
   outgoingMapper = new JMapper<>(TaskDto.class, Task.class, outgoingAPI);
   incomingMapper = new JMapper<>(Task.class, TaskDto.class, incomingAPI);
}

Usage

To use the mappers, we call getDestination with our source object and get our destination object back.

Task task = incomingMapper.getDestination(taskDto);

The getDestination method plays well in streams, so we can easily process a whole collection:

List<TaskDto> taskDtos = tasks.stream().map(task -> outgoingMapper.getDestination(task)).collect(Collectors.toList());

MapStruct

Now let’s take a look at the MapStruct framework.  MapStruct is unique in that we use it by creating interfaces and it then generates all of its mapping code at compile time.  The other mapping frameworks tend to use reflection or byte code generation.

Gradle Dependencies

implementation 'org.mapstruct:mapstruct:1.2.0.Final'
implementation 'org.mapstruct:mapstruct-processor:1.2.0.Final'

TaskMapper Interface

For our mapping between tasks, we need to create an interface and give it the @Mapper annotation.  We use MapStruct’s Mappers factory to get a single instance of our mapper.  Then we specify the two methods we need for outgoing and incoming mapping.

@Mapper
public interface TaskMapper {
   TaskMapper INSTANCE = Mappers.getMapper(TaskMapper.class);
   TaskDto outgoing(Task task);
   Task incoming(TaskDto taskDto);
}

Usage

Over in our controller class, we use the INSTANCE and methods we defined in our Interface to do our mapping.

Task task = TaskMapper.INSTANCE.incoming(taskDto);

And a whole list at once:

List<TaskDto> taskDtos = tasks.stream().map(task -> TaskMapper.INSTANCE.outgoing(task)).collect(Collectors.toList());

With MapStruct the conversion between our Enums and our Strings was handled and we don’t need any custom converters.

Dozer

Next we’ll take a look at Dozer, another framework for bean mapping that uses reflection.

A note of caution: When I Google Dozer, my first result is for Dozer’s old sourceforge site.  That gave the impression that it hasn’t been maintained since 2014.  That’s not true.  It’s been moved to GitHub and is actively developed.

Gradle Dependency

implementation 'com.github.dozermapper:dozer-core:6.4.1'

Configuration

Dozer recommends that there only be one instance of the DozerBeanMapper in an application, so the first thing we’ll do is create it as a bean in our ApplicationConfig, so that we can autowire it.

@Bean
public Mapper dozerBeanMapper() {
   return DozerBeanMapperBuilder.buildDefault();
}

Usage

Now that we’ve gotten our configuration taken care of, we can autowire the DozerBeanMapper.

@Autowired
private Mapper dozerBeanMapper;

Here’s a simple mapping:

Task task = dozerBeanMapper.map(taskDto, Task.class);

And here’s using map in a stream:

List<TaskDto> taskDtos = tasks.stream().map(task -> dozerBeanMapper.map(task, TaskDto.class)).collect(Collectors.toList());

As with MapStruct, Dozer handles the conversion between our Enums and our Strings without our having to write any custom converters.

Orika

Orika is a bean mapping framework that uses byte code generation.  Mappings are configured through an API like with JMapper.

Gradle Dependency

implementation 'ma.glasnost.orika:orika-core:1.5.2'

Configuration

With Orika, mapping is done through a MapperFacade.  The classes we want to map are configured using a MapperFactory and then we get the facade from the factory.  We’ll set this up in our Configuration class and expose the MapperFacade as a bean so we can autowire it.  The call to byDefault() indicates that we want to map all the fields.  It’s also possible to be specific about the fields.

@Bean 
public MapperFacade orikaMapper() {
   MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
   mapperFactory.classMap(Task.class, TaskDto.class)
      .byDefault()
      .register();
   mapperFactory.classMap(TaskDto.class, Task.class)
      .byDefault()
      .register();
   
   return mapperFactory.getMapperFacade();
}

Usage

Once configured, we can autowire our MapperFacade and use it for mapping.

@Autowired
private MapperFacade orikaMapper;

Mapping one object at a time:

Task task = orikaMapper.map(taskDto, Task.class);

And mapping a collection of objects using Stream:

List<TaskDto> taskDtos = tasks.stream().map(task -> orikaMapper.map(task, TaskDto.class)).collect(Collectors.toList());

Orika also did not require any custom converters for the mapping between our Priorities and Statuses Enums and the string fields in the TaskDto class.

Conclusion

In this post, I really just brushed the surface of four mapping frameworks: JMapper, MapStruct, Dozer and Orika.  They all do things a little differently and at the same time are strikingly similar in their usage.  I kept the example extremely simple on purpose, so it jumped out at me that JMapper required a custom conversion for the Enum fields and none of the others did.

I’m now curious to see how each of these frameworks is when we’re dealing with a more complicated mapping scenario.

I didn’t do any performance testing, but there’s an article over on Baeldung comparing the performance of these four frameworks and ModelMapper.  So if you want the fastest possible framework, that’s worth checking out.

The complete example code is available on GitHub.

Advertisements