The DTO Mapping Problem
Every Java developer has written this type of code hundreds of times:
// Manual mapping - the old way
public CustomerDto toDto(Customer entity) {
CustomerDto dto = new CustomerDto();
dto.setFirstName(entity.getFirstName());
dto.setLastName(entity.getLastName());
dto.setEmail(entity.getEmail());
// ... 20 more fields
return dto;
}
This boilerplate code is:
- Error-prone (easy to miss fields or make typos)
- Hard to maintain (changes require modifying both sides)
- Verbose (clutters your codebase)
Mapping Solutions Comparison
Approach | Pros | Cons |
---|---|---|
Manual Mapping | Full control, no dependencies | Boilerplate, maintenance hell |
Reflection-based (ModelMapper) | Automatic mapping | Slow runtime performance, magic behavior |
MapStruct | Compile-time generation, fast, debuggable | Slight learning curve |
Performance Benchmark (operations/second)
Getting Started with MapStruct
Maven Setup
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Gradle Setup
plugins {
id 'java'
}
dependencies {
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
}
Your First Mapper
Create a mapper interface:
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);
CustomerDto toDto(Customer customer);
}
MapStruct will generate this implementation at compile time:
// Generated code (visible in target/generated-sources)
public class CustomerMapperImpl implements CustomerMapper {
@Override
public CustomerDto toDto(Customer customer) {
if (customer == null) {
return null;
}
CustomerDto customerDto = new CustomerDto();
customerDto.setFirstName(customer.getFirstName());
customerDto.setLastName(customer.getLastName());
// ... all other fields
return customerDto;
}
}
Key Advantages
- No runtime reflection - Pure Java code
- Debuggable - You can step through the generated code
- Fast - Nearly identical to manual mapping
0 Comments