Since Mapstruct version 1.3.0.Final is out, we are able to better integrate with Lombok Builder pattern. Mapstruct is a library that takes away a lot of boilerplate code for mapping between POJO’s. With Mapstruct there is no need for implementing the real mapping itself.

With Lombok we can use a Builder pattern and mark an object as a Value(Object). It will result in an immutable object. This blog post shows how we can use Mapstruct to use the Builder pattern of Lombok.

Code generator libraries

  • Lombok is a code generator library that generates constructors, getters, setters, builders and many more. All features can be found here.

  • Mapstruct is a code generator library for bean mapping. The generated mapping code uses plain method invocations and thus is fast, type-safe and easy to understand.

With a few simple simple snippets we’ll see how these 2 libraries can be used together in a way that Mapstruct uses Lombok’s Builder methods.

Gradle build file

Make some changes to the build.gradle file to include the dependencies for Lombok and Mapstruct.

Listing 1. build.gradle
configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
    compileOnly {
        extendsFrom annotationProcessor
    }
    springinstrument
}

ext {
    set('mapstructVersion', '1.3.0.Final')
}

dependencies {
    compileOnly 'org.projectlombok:lombok'

    implementation "org.mapstruct:mapstruct-jdk8:${mapstructVersion}"

    annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" //Must be defined before the lombok annotationProcessor
    annotationProcessor 'org.projectlombok:lombok'
}

Lombok configuration

We add some instructions for Lombok in the lombok.config in the root of our project. It will be automatically be picked up.

Listing 2. lombok.config
lombok.addLombokGeneratedAnnotation = true
lombok.anyConstructor.addConstructorProperties = true

Domain class

As domain class we create a class Person. We will define this class with some attributes.

Listing 3. Person.java
package domain;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;

@Value
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE) //Hides the constructor to force useage of the Builder.
public final class Person {
	private final Long id;
	private final String firstName;
	private final String lastName;
}

External adapter class

Our person comes from an external system. The class ExternalPerson lives in the adapter.external package and should look like the following:

Listing 4. ExternalPerson.java
package adapter.external;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;

@Value
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE) //Hides the constructor to force useage of the Builder.
public final class ExternalPerson {
	private final Long id;
	private final String personFirstName;
	private final String personLastName;
}

Mapstruct mapper

Now we can create our specific PersonMapper interface with the mandatory Mapstruct annotations. We need to add some additional Mapping annotations to specify non-standard mappings of some fields.

Listing 5. PersonMapper.java
package adapter.external;

import domain.Person;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring") //Creates a Spring Bean automatically
interface PersonMapper {

    @Mapping(target = "firstName", source = "personFirstName")
    @Mapping(target = "lastName", source = "personLastName")
    Person toPerson(ExternalPerson externalPerson);
}

Now run the gradle build and have a look at the implementation of PersonMapper (PersonMapperImpl). It’s probably located in ./build/generated/sources/annotationProcessor/java/main

In this Mapper implementation you will notice the Lombok Builder of the Person is being used.

Listing 6. PersonMapperImpl.java
package adapter.external;

import domain.Person;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2019-07-29T10:31:26+0200",
    comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
@Component
class PersonMapperImpl implements PersonMapper {

    @Override
    public Person toPerson(ExternalPerson externalPerson) {
        if ( externalPerson == null ) {
            return null;
        }

        PersonBuilder person = Person.builder();

        person.id( externalPerson.getId() );
        person.firstName( externalPerson.getPersonFirstName() );
        person.lastName( externalPerson.getPersonLastName() );

        return person.build();
    }
}

Cool, now we have created an Immutable Object Person!

shadow-left