Backend Development 16 min read

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.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Using MapStruct for Object Mapping in Java: Basics, Advanced Features and Spring Integration

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.

JavaDTOSpringMapStructObject MappingAnnotation Processor
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.