Mastering MapStruct: Simplify Java DTO Mapping with Real-World Examples
Learn how to efficiently convert between Java entities and DTOs using MapStruct, covering basic mappings, default methods, abstract classes, multiple parameters, update operations, Spring integration, and custom type conversions, with complete code examples and explanations to streamline data transfer in backend applications.
1. What is MapStruct?
MapStruct is a compile‑time code generator that creates type‑safe mappers for converting between Java objects, such as entities and DTOs, eliminating the boilerplate of manual getter/setter copying.
2. Example Scenario
Suppose a User entity contains fields id, username, password, phoneNum, email, and a nested Role object. In a controller we only need id, username, and the role name.
@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;
}Manually copying these fields with getters and setters becomes cumbersome, especially when the number of properties grows.
3. Using MapStruct to Solve the Problem
Create a mapper interface that defines the mapping rules between User, Role, and UserRoleDto:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@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);
}Test the mapper:
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);
}
}The mapper automatically copies the required fields, greatly simplifying the code.
4. Adding Default Methods
Mapper interfaces can also contain default methods for custom logic:
@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);
default UserRoleDto defaultConvert() {
UserRoleDto dto = new UserRoleDto();
dto.setUserId(0L);
dto.setName("None");
dto.setRoleName("None");
return dto;
}
}Unit test can invoke defaultConvert() to obtain a placeholder DTO.
5. Using an Abstract Class Instead of an Interface
MapStruct also supports abstract classes as mappers:
@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() {
UserRoleDto dto = new UserRoleDto();
dto.setUserId(0L);
dto.setName("None");
dto.setRoleName("None");
return dto;
}
}6. Mapping Multiple Source Parameters
MapStruct can bind fields from several source objects:
@Mapper
public interface UserRoleMapper {
UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);
@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);
}7. Using Parameters Directly as Values
Parameters that are not objects can also be mapped:
@Mapper
public interface UserRoleMapper {
UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);
@Mappings({
@Mapping(source = "user.id", target = "userId"),
@Mapping(source = "user.username", target = "name"),
@Mapping(source = "myRoleName", target = "roleName")
})
UserRoleDto useParameter(User user, String myRoleName);
}8. Updating Existing Objects with @MappingTarget
Instead of creating a new DTO, MapStruct can update an existing instance:
public interface UserRoleMapper1 {
UserRoleMapper1 INSTANCES = Mappers.getMapper(UserRoleMapper1.class);
@Mappings({
@Mapping(source = "userId", target = "id"),
@Mapping(source = "name", target = "username"),
@Mapping(source = "roleName", target = "role.roleName")
})
void updateDto(UserRoleDto dto, @MappingTarget User user);
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "username", target = "name"),
@Mapping(source = "role.roleName", target = "roleName")
})
void update(User user, @MappingTarget UserRoleDto dto);
}9. Mapping Without Getters/Setters
Even classes without accessor methods can be mapped by directly accessing fields:
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);
@Mapping(source = "customerName", target = "name")
Customer toCustomer(CustomerDto dto);
@InheritInverseConfiguration
CustomerDto fromCustomer(Customer customer);
}10. Spring Integration
Setting componentModel = "spring" registers the mapper as a Spring bean:
@Mapper(componentModel = "spring")
public interface CustomerMapper {
@Mapping(source = "name", target = "customerName")
CustomerDto toCustomerDto(Customer customer);
}It can then be injected with @Autowired in any Spring component.
11. Custom Type Conversions
When source and target types differ, a helper class can be referenced via uses:
public class BooleanStrFormat {
public String toStr(Boolean isDisable) {
return isDisable ? "Y" : "N";
}
public Boolean toBoolean(String str) {
return "Y".equals(str);
}
} @Mapper(uses = {BooleanStrFormat.class})
public interface CustomerMapper {
CustomerMapper INSTANCES = Mappers.getMapper(CustomerMapper.class);
@Mappings({
@Mapping(source = "name", target = "customerName"),
@Mapping(source = "isDisable", target = "disable"))
})
CustomerDto toCustomerDto(Customer customer);
}When the mapper is used in a Spring context, the helper class should also be a Spring bean and will be injected automatically.
These examples demonstrate how MapStruct can dramatically reduce boilerplate code for object conversion, support default and custom methods, work with multiple sources, update existing instances, and integrate seamlessly with Spring.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
