Simplify Model Mapping in Spring Boot with MapStruct and Lombok
Introduction:
In Spring Boot applications, one common task is mapping data between different layers of an application, such as DTOs (Data Transfer Objects) and entities. This mapping process can be repetitive and time-consuming, but luckily, there are powerful libraries available to simplify the task. In this blog, we’ll explore how to leverage MapStruct and Lombok together to streamline mapping operations in a Spring Boot project.
What is MapStruct?
MapStruct — Java bean mappings, the easy way!
MapStruct is a Java-based code generation library that automates the process of mapping between Java beans. It generates type-safe mapping code at compile time, eliminating the need for manual mapping code. MapStruct supports various mapping strategies, including property-based mapping, mapping with expressions, and custom converters.
Why use Lombok with MapStruct?
Lombok is another popular Java library that helps reduce boilerplate code by automatically generating getter, setter, equals, hashCode, and other methods. When used in conjunction with MapStruct, Lombok can further simplify mapping code by eliminating the need to write constructor code, getters, and setters for the mapped classes.
Setting Up the Project:
To get started, create a new Spring Boot project or use an existing one. Make sure you have the necessary dependencies in your pom.xml
or build.gradle
file:
<!-- Maven -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
// Gradle
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
compileOnly 'org.projectlombok:lombok:1.18.20'
Configure mapstruct to work with lombok:
To generate mapping correctly using mapstruct
while also using lombok
requires an extra step of configuration
, else it will not generate the mappings at all.
Add plugin maven-compiler-plugin
and configure it to use lombok-mapstruct-bindings
:
// pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>
-Amapstruct.defaultComponentModel=spring
</compilerArg>
</compilerArgs>
</configuration>
</plugin>
Defining Mappings:
Let’s assume we have two classes: Product
and ProductDto
. The Product class represents the entity in our application, while the ProductDto
class is used for data transfer purposes.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
private Long id;
private String name;
private String description;
private BigDecimal price;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductDto {
private Long id;
private String name;
private String description;
private String formattedPrice;
}
Using MapStruct and Lombok:
To generate the mapping code between Product
and ProductDto
, we need to define a mapper interface and annotate it with @Mapper
from MapStruct. Additionally, annotate the mapper interface with @Mapper(componentModel = “spring”)
to make it a Spring component.
@Mapper(componentModel = "spring")
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
@Mapping(source = "price", target = "formattedPrice")
ProductDto toDto(Product product);
}
With Lombok, we can simplify the ProductDto
class by removing the explicit constructor and getters/setters:
@Data
public class ProductDto {
private Long id;
private String name;
private String description;
private String formattedPrice;
}
Using the Mapper:
Now that we have defined the mapper interface, we can use it in our Spring Boot application. Inject the ProductMapper
into the desired component or service and use it to perform the mapping.
@Service
public class ProductService {
private final ProductMapper productMapper;
public ProductService(ProductMapper productMapper) {
this.productMapper = productMapper;
}
public ProductDto getProductDto(Product product) {
return productMapper.toDto(product);
}
}
In the above example, the getProductDto()
method takes a Product
instance and maps it to a ProductDto
using the ProductMapper
interface. The mapped ProductDto
object is then returned.
Conclusion:
By combining the power of MapStruct and Lombok in a Spring Boot project, we can significantly simplify the mapping process. MapStruct generates the mapping code at compile time, reducing manual effort and ensuring type safety. Lombok eliminates boilerplate code, further enhancing the developer experience. Together, these libraries provide a clean and efficient way to handle complex mapping scenarios in Spring Boot applications.
Remember to configure your IDE to enable annotation processing for Lombok and rebuild your project to ensure that the mapping code is generated correctly.
With MapStruct and Lombok, you can focus on the business logic of your Spring Boot application, leaving the mapping code to be generated automatically. This approach improves code readability, maintainability, and developer productivity, making your Spring Boot development experience even more enjoyable.