Mastering MapStruct: Boost Java Object Mapping Beyond BeanUtils
MapStruct is a powerful Java annotation‑based mapper that outperforms BeanUtils by generating compile‑time mapping implementations without reflection, supporting simple, nested, collection, and custom mappings, dependency injection, constants, expressions, and error handling, making object‑DTO conversions efficient and maintainable in Spring Boot projects.
About BeanUtils
When converting PO, VO, DTO objects, BeanUtils works for simple cases but has several drawbacks:
Uses reflection, resulting in low performance.
Cannot map properties with different names or types without writing getters/setters.
Nested objects require manual handling.
Collection conversion needs explicit loops.
MapStruct addresses all these issues.
MapStruct Overview
MapStruct is a Java annotation‑based object‑mapping tool with over 6.9K stars on GitHub. By defining mapping rules in an interface, it generates implementation classes at compile time, avoiding reflection and delivering excellent performance for complex mappings.
IDEA Plugin Support
MapStruct provides a dedicated IDEA plugin; install it before using the tool.
Project Integration
Add the following Maven dependencies (version 1.4.2.Final) to integrate MapStruct into a Spring Boot project:
<code><dependency>
<!-- MapStruct dependencies -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
</dependencies></code>Basic Usage
Define a member PO class and a corresponding DTO:
<code>/**
* Shopping member
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Member {
private Long id;
private String username;
private String password;
private String nickname;
private Date birthday;
private String phone;
private String icon;
private Integer gender;
}</code> <code>/**
* Member DTO
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberDto {
private Long id;
private String username;
private String password;
private String nickname;
// Different type property
private String birthday;
// Different name property
private String phoneNumber;
private String icon;
private Integer gender;
}</code>Create a mapper interface:
<code>@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
@Mapping(source = "phone", target = "phoneNumber")
@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
MemberDto toDto(Member member);
}</code>Use the mapper in a controller to test the conversion:
<code>@RestController
@Api(tags = "MapStructController", description = "MapStruct object conversion test")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "Basic Mapping")
@GetMapping("/baseMapping")
public CommonResult baseTest() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
MemberDto memberDto = MemberMapper.INSTANCE.toDto(memberList.get(0));
return CommonResult.success(memberDto);
}
}
</code>Collection Mapping
MapStruct can map a list of PO objects to a list of DTOs directly:
<code>@Mapper
public interface MemberMapper {
// ... previous methods
List<MemberDto> toDtoList(List<Member> list);
}
</code> <code>@ApiOperation(value = "Collection Mapping")
@GetMapping("/collectionMapping")
public CommonResult collectionMapping() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
List<MemberDto> memberDtoList = MemberMapper.INSTANCE.toDtoList(memberList);
return CommonResult.success(memberDtoList);
}
</code>Nested Object Mapping
MapStruct also supports mapping of nested objects, such as an Order containing Member and Product objects:
<code>public class Order {
private Long id;
private String orderSn;
private Date createTime;
private String receiverAddress;
private Member member;
private List<Product> productList;
}
</code> <code>public class OrderDto {
private Long id;
private String orderSn;
private Date createTime;
private String receiverAddress;
private MemberDto memberDto;
private List<ProductDto> productDtoList;
}
</code> <code>@Mapper(uses = {MemberMapper.class, ProductMapper.class})
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
@Mapping(source = "member", target = "memberDto")
@Mapping(source = "productList", target = "productDtoList")
OrderDto toDto(Order order);
}
</code>Composite Mapping
MapStruct can merge properties from multiple source objects into a single target object:
<code>@Mapper
public interface MemberMapper {
// ... previous methods
@Mapping(source = "member.phone", target = "phoneNumber")
@Mapping(source = "member.birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
@Mapping(source = "member.id", target = "id")
@Mapping(source = "order.orderSn", target = "orderSn")
@Mapping(source = "order.receiverAddress", target = "receiverAddress")
MemberOrderDto toMemberOrderDto(Member member, Order order);
}
</code>Advanced Usage
Dependency Injection
Configure the mapper with
componentModel = "spring"so that Spring can inject it as a bean:
<code>@Mapper(componentModel = "spring")
public interface MemberSpringMapper {
@Mapping(source = "phone", target = "phoneNumber")
@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
MemberDto toDto(Member member);
}
</code> <code>@Autowired
private MemberSpringMapper memberSpringMapper;
</code>Constants, Default Values, and Expressions
MapStruct allows setting constant values, default values, and Java expressions during mapping:
<code>@Mapper(imports = {UUID.class})
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
@Mapping(target = "id", constant = "-1L")
@Mapping(source = "count", target = "count", defaultValue = "1")
@Mapping(target = "productSn", expression = "java(UUID.randomUUID().toString())")
ProductDto toDto(Product product);
}
</code>Custom Pre‑ and Post‑Mapping Logic
Define an abstract mapper with
@BeforeMappingand
@AfterMappingmethods to apply custom logic:
<code>@Mapper(imports = {UUID.class})
public abstract class ProductRoundMapper {
public static final ProductRoundMapper INSTANCE = Mappers.getMapper(ProductRoundMapper.class);
@Mapping(target = "id", constant = "-1L")
@Mapping(source = "count", target = "count", defaultValue = "1")
@Mapping(target = "productSn", expression = "java(UUID.randomUUID().toString())")
public abstract ProductDto toDto(Product product);
@BeforeMapping
public void beforeMapping(Product product) {
// If price < 0, set to 0
if (product.getPrice().compareTo(BigDecimal.ZERO) < 0) {
product.setPrice(BigDecimal.ZERO);
}
}
@AfterMapping
public void afterMapping(@MappingTarget ProductDto productDto) {
// Set current time as createTime
productDto.setCreateTime(new Date());
}
}
</code>Exception Handling
Create a custom validator and let the mapper throw a checked exception when validation fails:
<code>public class ProductValidator {
public BigDecimal validatePrice(BigDecimal price) throws ProductValidatorException {
if (price.compareTo(BigDecimal.ZERO) < 0) {
throw new ProductValidatorException("Price cannot be less than 0!");
}
return price;
}
}
</code> <code>@Mapper(uses = {ProductValidator.class}, imports = {UUID.class})
public interface ProductExceptionMapper {
ProductExceptionMapper INSTANCE = Mappers.getMapper(ProductExceptionMapper.class);
@Mapping(target = "id", constant = "-1L")
@Mapping(source = "count", target = "count", defaultValue = "1")
@Mapping(target = "productSn", expression = "java(UUID.randomUUID().toString())")
ProductDto toDto(Product product) throws ProductValidatorException;
}
</code>Conclusion
MapStruct provides a robust, compile‑time solution for object mapping in Java, eliminating the boilerplate code required by BeanUtils and offering extensive features such as nested, collection, custom, and error‑handling mappings, making it an essential tool for backend development.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.