Effectively use Mapstruct and Lombok's builder
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.
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.
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.
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:
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.
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.
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
!