Why Spring BeanUtils.copyProperties Is Discouraged and MapStruct Is Preferred for Java Object Mapping
The article explains the limitations of Spring's BeanUtils.copyProperties—such as type mismatches, null overwriting, and reflection overhead—and demonstrates how MapStruct provides a faster, compile‑time generated alternative for copying properties between Java objects, with usage examples and Maven setup.
In typical business development, developers often need to copy properties between BO, PO, DTO objects, and many resort to Spring's BeanUtils.copyProperties to reduce manual getter/setter code.
However, this approach has several drawbacks:
Inconsistent property types cause copy failures (e.g., Long vs String IDs).
Primitive vs wrapper mismatches can trigger exceptions, especially with boolean fields named with an is prefix.
Null values overwrite existing data , leading to data loss when the source contains nulls.
Reflection‑based copying incurs performance penalties.
Incorrect imports may cause copy anomalies.
MapStruct is introduced as a high‑performance alternative because it generates mapping code at compile time, eliminating reflection overhead and running significantly faster than BeanUtils.
Why MapStruct Is Faster
Avoids reflection operations.
Uses pre‑compiled, efficient code.
MapStruct generates the mapping implementation during project build, for example:
@Generated(value = "org.mapstruct.ap.MappingProcessor",
date = "2024-02-07T00:50:05+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_211 (Oracle Corporation)")
public class XXXDTOConverterImpl implements XXXDTOConverter {
@Override
public XXXBO convertDtoToBo(XXXDTO XXXDTO) {
if ( XXXDTO == null ) {
return null;
}
XXXBO XXXBO = new XXXBO();
XXXBO.setId( XXXDTO.getId() );
XXXBO.setLabelName( XXXDTO.getLabelName() );
XXXBO.setSortNum( XXXDTO.getSortNum() );
XXXBO.setCategoryId( XXXDTO.getCategoryId() );
return XXXBO;
}
}How to Use MapStruct
Add Maven dependencies:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>Define DTO and BO classes (both implementing Serializable ) and a mapper interface:
@Data
public class SubjectLabelDTO implements Serializable {
private String labelName;
}
@Data
public class SubjectLabelBO implements Serializable {
private String labelName;
} @Mapper
public interface SubjectLabelDTOConverter {
SubjectLabelDTOConverter INSTANCE = Mappers.getMapper(SubjectLabelDTOConverter.class);
SubjectLabelBO convertDtoToBo(SubjectLabelDTO subjectLabelDTO);
}Use the mapper in service code:
public Result
add( SubjectLabelDTO subjectLabelDTO ) {
SubjectLabelBO subjectLabelBO = SubjectLabelDTOConverter.INSTANCE.convertDtoToBo(subjectLabelDTO);
// further processing
}Summary
MapStruct also performs shallow copies; for deep copies you need additional converters. When used together with Lombok, ensure Lombok’s dependency is declared after MapStruct to avoid null‑value issues during generated copying.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.