Announcing MapStruct

It is my pleasure to announce the first release of the MapStruct project!

MapStruct is a code generator which simplifies the implementation of mappings between Java bean types by generating mapping code at compile time, following a convention-over-configuration approach.

The problem

In multi-layered applications there is often the need to map between objects from diffent models.

Examples include the conversion from JPA entities into data transfer or view objects and the mapping between the internal model of an application and external-facing representations such as the types of a web service facade.

Writing the required mapping code from hand can be an error-prone and tedious task. MapStruct aims at simplifying this work by automating it as much as possible.

The solution

MapStruct is an annotation processor which is plugged into the Java compiler and generates mapping code at build time.

Unlike reflection-based mapping solutions, the mapping code generated by MapStruct uses plain method invocations for propagating values from the source to the target model and thus is fast, type-safe and easy to understand. This approach also allows for fast developer feedback; for instance you will get an error report at build time if two entities or attributes can’t be mapped.

Some code, please

It’s time to have a look at some code. Let’s assume there as an entity Car and an accompanying data transfer object.

public class Car {

    private String make;
    private int numberOfSeats;
    private Date manufacturingDate;
    private Person driver;
    private Category category; //an enum; SEDAN, CONVERTIBLE etc.

    //constructor, getters, setters etc.
}
public class CarDto {

    private String make;
    private int seatCount;
    private Date manufacturingYear;
    private PersonDto driver;
    private String category;

    //constructor, getters, setters etc.
}

Both types are structure-wise quite similar, with only a few differences:

  • the attribute representing the number of seats is named numberOfSeats in the entity but seatCount in the DTO
  • the attribute representing the manufacturing year is once named manufacturingDate and once manufacturingYear
  • the category attribute is an enum in the entity but a string in the DTO
  • the object referenced by the driver attribute is once of type Person (another entity) and once of type PersonDto

Now let’s see how to generate a mapper for these objects. To do so, just define a plain Java interface which declares a method for each required mapping:

@Mapper
public interface CarMapper {

    @Mappings({
        @Mapping(source = "numberOfSeats", target = "seatCount"),
        @Mapping(source = "manufacturingDate", target = "manufacturingYear")
    })
    CarDto carToCarDto(Car car);

    PersonDto personToPersonDto(Person person);
}

The @Mapper annotation marks the interface as mapping interface and lets the MapStruct processor kick in during compilation (see the reference documentation to learn how to hook the processor into your build). You can have several mapper interfaces as per your requirements, e.g. one per application module.

Each mapping methods expects the source object as parameter and returns the target object. The names can be freely chosen. For attributes with different names in source and target object, the @Mapping annotation can be used to configure the names.

When mapping attributes with different types in source and target bean, MapStruct will first look whether another mapping method for these types exists. So for instance the implementation of the carToCarDto() method will invoke personToPersonDto() to convert the referenced driver from Person to PersonDto.

If no mapping method for the required types exist, an automatic conversion will be applied if possible; e.g. the category attribute will be converted from the enumeration type into a string. There are many type conversions built into MapStruct by default but you also can easily implement custom mapping methods if required. Refer to Data type conversions for the details.

To obtain an instance of a mapper the method Mappers#getMapper() can be used. By convention, each mapper interface defines a member named INSTANCE holding an instance of the mapper type:

@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

    //...
}

In the future it will also be possible to obtain mapper objects using dependency injection containers such as CDI or Spring.

Finally let’s have a look at how the mapper is used to convert a Car object into a CarDto:

@Test
public void shouldMapAttributeByName() {
    //given
    Car car = new Car(
        "Morris",
        2,
        new GregorianCalendar( 1980, 0, 1 ).getTime(),
        new Person( "Bob" ),
        Category.SEDAN
    );

    //when
    CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );

    //then
    assertThat( carDto ).isNotNull();
    assertThat( carDto.getMake() ).isEqualTo( "Morris" );
    assertThat( carDto.getSeatCount() ).isEqualTo( 2 );
    assertThat( carDto.getManufacturingYear() ).isEqualTo( new GregorianCalendar( 1980, 0, 1 ).getTime() );
    assertThat( carDto.getDriver().getName() ).isEqualTo( "Bob" );
    assertThat( carDto.getCategory() ).isEqualTo( "SEDAN" );
}

Note that a dedicated mapper interface centered around the types of your application makes mappings type-safe, so unlike with a generic mapping API you can’t accidentally map e.g. a Car into a PersonDto.

The test also shows that the implementation of the carToCarDto() method handles the property name mappings (e.g. numberOfSeats vs. seatCount) as well as type conversions (for the driver and category attributes).

Trying it out youself

You want to give MapStruct a try? That’s great! You can download the distribution containing source and binaries from SourceForge. MapStruct is released under the Apache Software License 2.0.

If you’re using Maven or a similar dependency manager, you also can obtain MapStruct from Maven Central. The GAV coordinates are

  • org.mapstruct:mapstruct:1.0.0.Alpha1 for the core module containing the annotations and
  • org.mapstruct:mapstruct-processor:1.0.0.Alpha1 for the annotation processor module.

Check out the reference documentation to learn about all the ins and outs of MapStruct, including features such as collection mappings, reverse mappings or making use of hand-written mapping methods.

If you’re are stuck, come and join the MapStruct google-group and ask your questions there. This is also the right place to discuss any feature requests or other suggestions. And should you find a bug, please file a report in our issue tracker.

What’s next?

MapStruct is just in its beginnings. Today’s Alpha1 release is the first of a series of preview releases towards 1.0.0.Final.

If you have any feedback on MapStruct in general or certain features in particular make sure to let us know either by commenting here or by posting to the Google group.

On the roadmap for the next releases are improved support for collection mappings, mappings several source objects into one target object and mapping into immutable objects via constructor invocations.

You like MapStruct and want to contribute? That’s awesome! MapStruct is in a very early stage and your participation can make a big difference. The sources are hosted on GitHub, and Contributing has all the infos to get you started with hacking on MapStruct. Many thanks to Andreas who stepped up and contributed to the Alpha1 release!

comments powered by Disqus