That’s How You Can Use MapStruct With Lombok in Your Spring Boot Application
In this article, you will find code examples and explanations on how to work with MapStruct, Lombok, and Spring Boot in an efficient way.
Join the DZone community and get the full member experience.
Join For FreeHi! My name is Viacheslav Aksenov, and I am a senior software developer. Most often, I have to write code using Java and Kotlin. I have accumulated many examples of how to simplify boilerplate code using simple but effective libraries.
Introduction
When you implement services of any size, you often need to move data from one structure to another. Often this is the same data that is used on different layers of logic — in business logic, at the database level, or at the controller level for transfer to front-end applications.
To transfer this data, you have to repeat a lot of boilerplates. It's exhausting. I want to draw your attention to a library that will help you save your energy. Meet MapStruct!
With this library, you can only specify the structure mapping scheme. And the implementation will be collected by the library itself.
Where To Find It
The latest release can be found in Maven central repository.
You can add it to your pom.xml
:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
And you need to add an annotation processor to your plugins:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
If you use Gradle, it can be added to your build.gradle
:
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
How It Works
Let's take some data classes for example:
public class CatEntity {
private Long id;
private String name;
private String color;
// getters and setters
}
public class CatDto {
private Long id;
private String name;
private String color;
// getters and setters
}
And that's all code we need to write for its implementation:
@Mapper
public interface CatMapper {
CatEntity toEntity(CatDto dto);
CatDto toDto(CatEntity entity);
}
The implementation of this interface will be created by MapStruct itself:
@Generated
public class CatMapperImpl implements CatMapper {
@Override
public CatEntity toEntity(CatDto dto) {
if ( dto == null ) {
return null;
}
CatEntity catEntity = new CatEntity();
catEntity.setId( dto.getId() );
catEntity.setName( dto.getName() );
catEntity.setColor( dto.getColor() );
return catEntity;
}
@Override
public CatDto toDto(CatEntity entity) {
if ( entity == null ) {
return null;
}
CatDto catDto = new CatDto();
catDto.setId( entity.getId() );
catDto.setName( entity.getName() );
catDto.setColor( entity.getColor() );
return catDto;
}
}
How To Use MapStruct With Java’s Records
In Java 14, there were added record classes. MapStruct can handle them too:
public class CatEntity {
private Long id;
private String name;
private String color;
// getters and setters
}
public record CatRecord(
Long id,
String name,
String color
) {
}
And if we create an interface for mapping:
@Mapper
public interface CatRecordMapper {
CatEntity toEntity(CatRecord record);
CatRecord toRecord(CatEntity entity);
}
Then Mapstruct will generate the realization as:
@Generated
public class CatRecordMapperImpl implements CatRecordMapper {
@Override
public CatEntity toEntity(CatRecord record) {
if ( record == null ) {
return null;
}
CatEntity catEntity = new CatEntity();
catEntity.setId( record.id() );
catEntity.setName( record.name() );
catEntity.setColor( record.color() );
return catEntity;
}
@Override
public CatRecord toRecord(CatEntity entity) {
if ( entity == null ) {
return null;
}
Long id = null;
String name = null;
String color = null;
id = entity.getId();
name = entity.getName();
color = entity.getColor();
CatRecord catRecord = new CatRecord( id, name, color );
return catRecord;
}
}
How To Use MapStruct With Project Lombok
In the Java world, there is a big and widely known library — Project Lombok. It also allows for a decrease in boilerplate code that developers have to write. More details about this library you can find on the official website.
To add this library to your project, you need to add it to your pom.xml:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
And also, you need to add it to annotation processors:
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</path>
</annotationProcessorPaths>
For Gradle, it is a little bit simpler. Just add it to build.gradle
implementation 'org.projectlombok:lombok:1.18.28'
annotationProcessor "org.projectlombok:lombok:1.18.28"
And an important step! To integrate Project Lombok with MapStruct, you need to add a binding library:
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
For our example, it is enough to use @Data
annotation, which adds getters and setters.
@Data
public class CatDto {
private Long id;
private String name;
private String color;
}
@Data
public class CatEntity {
private Long id;
private String name;
private String color;
}
For the same mapper interface, it will generate the following implementation:
public interface CatMapper {
CatEntity toEntity(CatDto dto);
CatDto toDto(CatEntity entity);
}
@Generated
public class CatMapperImpl implements CatMapper {
@Override
public CatEntity toEntity(CatDto dto) {
if ( dto == null ) {
return null;
}
CatEntity catEntity = new CatEntity();
catEntity.setId( dto.getId() );
catEntity.setName( dto.getName() );
catEntity.setColor( dto.getColor() );
return catEntity;
}
@Override
public CatDto toDto(CatEntity entity) {
if ( entity == null ) {
return null;
}
CatDto catDto = new CatDto();
catDto.setId( entity.getId() );
catDto.setName( entity.getName() );
catDto.setColor( entity.getColor() );
return catDto;
}
}
How To Add Spring Boot Logic Into Mapper
Sometimes it is required to retrieve some fields from Spring's beans. Let's assume that we don't store weight information in the database, which is not reachable in Entity. Instead of it, we have some Spring's service that provides this information.
@Data
public class CatDto {
private Long id;
private String name;
private String color;
private Integer weight;
}
@Data
public class CatEntity {
private Long id;
private String name;
private String color;
}
And there is service that provides weight information:
@Service
public class CatWeightProvider {
public Integer getWeight(String name) {
// some logic for retrieving weight info
return 5;
}
}
To use this bean to retrieve weight info inside the mapper interface should be replaced with an abstract class with the method that describes all additional logic.
@Mapper(componentModel = "spring")
public abstract class CatMapper {
@Autowired
private CatWeightProvider provider;
@Mapping(target = "weight", source = "entity.name", qualifiedByName = "retrieveWeight")
public abstract CatDto toDto(CatEntity entity);
@Named("retrieveWeight")
protected Integer retrieveWeight(String name) {
return provider.getWeight(name);
}
}
In this case, MapStruct will generate the realization of this abstract class:
@Generated
@Component
public class CatMapperImpl extends CatMapper {
@Override
public CatDto toDto(CatEntity entity) {
if ( entity == null ) {
return null;
}
CatDto catDto = new CatDto();
catDto.setWeight( retrieveWeight( entity.getName() ) );
catDto.setId( entity.getId() );
catDto.setName( entity.getName() );
catDto.setColor( entity.getColor() );
return catDto;
}
}
How To Ignore Field and Map Fields With Different Names
For example, fields in the database entity and fields in dto have different names.
And for example, we need to ignore field weight in dto layer.
@Data
public class CatDto {
private String name;
private String color;
private Integer weight;
}
@Data
public class CatEntity {
private Long idInDatabase;
private String nameInDatabase;
private String colorInDatabase;
}
It can be done with the following parameters:
@Mapper
public interface CatMapper {
@Mapping(target = "weight", ignore = true)
@Mapping(target = "name", source = "entity.nameInDatabase")
@Mapping(target = "color", source = "entity.colorInDatabase")
CatDto toDto(CatEntity entity);
}
So, implementation from MapStruct will be:
@Generated
public class CatMapperImpl implements CatMapper {
@Override
public CatDto toDto(CatEntity entity) {
if ( entity == null ) {
return null;
}
CatDto catDto = new CatDto();
catDto.setName( entity.getNameInDatabase() );
catDto.setColor( entity.getColorInDatabase() );
return catDto;
}
}
Conclusion
We have covered the most popular scenarios that arise when developing applications with multiple layers. Thus, the combination of Project Lombok and MapStruct libraries can significantly save the developer's time and effort on boilerplates. You can find code examples on my GitHub.
Thank you for your attention!
Opinions expressed by DZone contributors are their own.
Comments