Using MapStruct for Object Mapping in Java: Basics, Advanced Features and Spring Integration
This article explains how MapStruct, a Java annotation‑processor library, can automatically generate mapper implementations to convert between entities and DTOs, covering basic usage, default methods, abstract classes, multiple source parameters, update operations, handling fields without getters/setters, Spring injection and custom type conversions.
MapStruct is a compile‑time code generator that creates mapper implementations for converting between Java objects (e.g., entities and DTOs), eliminating repetitive getter/setter copying.
First, simple domain classes are defined:
@AllArgsConstructor
@Data
public class User {
private Long id;
private String username;
private String password;
private String phoneNum;
private String email;
private Role role;
}
@AllArgsConstructor
@Data
public class Role {
private Long id;
private String roleName;
private String description;
}
@Data
public class UserRoleDto {
/** user id */
private Long userId;
/** username */
private String name;
/** role name */
private String roleName;
}A manual test shows the boiler‑plate required to copy values:
public class MainTest {
User user = null;
@Before
public void before() {
Role role = new Role(2L, "administrator", "超级管理员");
user = new User(1L, "zhangsan", "12345", "17677778888", "[email protected]", role);
}
@Test
public void test1() {
UserRoleDto dto = new UserRoleDto();
dto.setUserId(user.getId());
dto.setName(user.getUsername());
dto.setRoleName(user.getRole().getRoleName());
System.out.println(dto);
}
}MapStruct removes this boiler‑plate by defining a mapper interface with mapping rules:
@Mapper
public interface UserRoleMapper {
UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "username", target = "name"),
@Mapping(source = "role.roleName", target = "roleName")
})
UserRoleDto toUserRoleDto(User user);
}The generated implementation automatically copies the three fields, as demonstrated in a test that simply calls UserRoleMapper.INSTANCES.toUserRoleDto(user) .
Advanced features include adding default methods to the mapper:
@Mapper
public interface UserRoleMapper {
// ... previous method
default UserRoleDto defaultConvert() {
UserRoleDto dto = new UserRoleDto();
dto.setUserId(0L);
dto.setName("None");
dto.setRoleName("None");
return dto;
}
}MapStruct can also be expressed as an abstract class instead of an interface:
@Mapper
public abstract class UserRoleMapper {
public static final UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "username", target = "name"),
@Mapping(source = "role.roleName", target = "roleName")
})
public abstract UserRoleDto toUserRoleDto(User user);
public UserRoleDto defaultConvert() { /* same as above */ }
}Multiple source parameters are supported, allowing values from two objects to be merged into one DTO:
@Mappings({
@Mapping(source = "user.id", target = "userId"),
@Mapping(source = "user.username", target = "name"),
@Mapping(source = "role.roleName", target = "roleName")
})
UserRoleDto toUserRoleDto(User user, Role role);Updating an existing target object is possible with @MappingTarget :
@Mappings({
@Mapping(source = "userId", target = "id"),
@Mapping(source = "name", target = "username"),
@Mapping(source = "roleName", target = "role.roleName")
})
void updateDto(UserRoleDto dto, @MappingTarget User user);Even fields without getters/setters can be mapped by using public fields in the target class and the @InheritInverseConfiguration annotation:
@Mapper
public interface CustomerMapper {
@Mapping(source = "customerName", target = "name")
Customer toCustomer(CustomerDto dto);
@InheritInverseConfiguration
CustomerDto fromCustomer(Customer customer);
}Spring integration is achieved by setting componentModel = "spring" on the mapper, which registers the generated implementation as a Spring bean:
@Mapper(componentModel = "spring")
public interface CustomerMapper {
CustomerDto toCustomerDto(Customer customer);
}
// Usage in a Spring component
@Autowired
private CustomerMapper mapper;Custom type conversions can be added via the uses attribute. The example converts a Boolean field to a String ("Y"/"N") using a helper class:
public class BooleanStrFormat {
public String toStr(Boolean b) { return b ? "Y" : "N"; }
public Boolean toBoolean(String s) { return "Y".equals(s); }
}
@Mapper(uses = { BooleanStrFormat.class })
public interface CustomerMapper {
@Mappings({
@Mapping(source = "name", target = "customerName"),
@Mapping(source = "isDisable", target = "disable")
})
CustomerDto toCustomerDto(Customer customer);
}When the mapper is configured for Spring, the helper class is also injected automatically.
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.