Backend Development 16 min read

MapStruct Tutorial: Introduction, Setup, and Advanced Usage

This article introduces MapStruct, explains why JavaBean conversion is challenging, shows how to add Maven dependencies, define POJOs and DTOs, create mapper interfaces, run tests, and explores its performance benefits, simple usage, debugging advantages, and advanced features such as custom mappings, multi‑source mapping, bean updates, and integration with dependency injection.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
MapStruct Tutorial: Introduction, Setup, and Advanced Usage

1. What is MapStruct

MapStruct is an annotation processor that generates type‑safe, high‑performance, dependency‑free Java bean mapping code.

1.1 The JavaBean conversion problem

Converting between JavaBeans often requires repetitive boiler‑plate code or reflection‑based utilities like BeanUtils, which can hurt performance and require matching field names.

1.2 Changes brought by MapStruct

Annotation processor

Generates mapping code automatically

Type‑safe, high‑performance, no runtime dependencies

2. Getting Started with MapStruct

2.1 Adding dependencies

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.1.0</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

2.2 POJO class

@Data
public class User {
    private Integer id;
    private String name;
    private String address;
    private Date birth;
}

2.3 DTO class

@Data
public class UserDto implements Serializable {
    private Integer id;
    private String name;
    private String address;
    private Date birth;
}

2.4 Mapper interface

@Mapper
public interface UserMapper {
    UserDto userToUserDto(User user);
    List
userToUserDto(List
users);
}

2.5 Test method

@Test
public void userPoToUserDto() {
    User user = new User();
    user.setId(1);
    user.setName("myx");
    user.setAddress("河北沧州");
    user.setBirth(new Date());
    UserMapper mapper = Mappers.getMapper(UserMapper.class);
    UserDto userDto = mapper.userToUserDto(user);
    System.out.println(userDto);
}

2.6 Execution result

(Image showing the console output of the generated DTO)

3. Advantages of MapStruct

3.1 High performance

Unlike reflection, the generated code runs as fast as hand‑written mapping code.

3.2 Simple to use

For fully matching fields, no extra configuration is needed; for special cases, @Mapping annotations handle custom logic.

3.3 No runtime dependency

The generated code is independent of MapStruct at runtime.

3.4 Easy debugging

Since the mapper implementation is ordinary Java code, you can set breakpoints and step through it.

4. Practical MapStruct Scenarios

4.1 Same property names

When source and target fields share the same name, MapStruct maps them automatically.

4.2 Different property names

Use @Mapping to specify source‑target pairs.

@Mapper
public interface UserMapper {
    @Mappings({
        @Mapping(source = "pwd", target = "password"),
        @Mapping(source = "subUser", target = "subUserDto")
    })
    UserDto userToUserDto(User user);
}

4.3 Mapping non‑primitive types

Nested objects and collections are handled automatically if their element types also have mappers.

4.4 Custom conversion methods

Define default methods in the mapper interface for cases that cannot be generated.

@Mapper
public interface UserMapper {
    @Mappings({
        @Mapping(source = "pwd", target = "password"),
        @Mapping(source = "subUser", target = "subUserDto")
    })
    UserDto userToUserDto(User user);

    default SubUserDto subSource2subTarget(SubUser subUser) {
        if (subUser == null) return null;
        SubUserDto dto = new SubUserDto();
        dto.setResult(!subUser.getDeleted().equals(0));
        dto.setName(subUser.getName() == null ? "" : subUser.getName());
        return dto;
    }
}

4.5 Multi‑source mapping (many‑to‑one)

MapStruct can map several source parameters to a single target.

@Mapper
public interface UserMapper {
    @Mappings({
        @Mapping(source = "user.pwd", target = "password"),
        @Mapping(source = "subUser.name", target = "name")
    })
    NewUserDto userToUserDto(User user, SubUser subUser);
}

4.6 Updating an existing bean

Use @MappingTarget to modify the target instance instead of creating a new one.

@Mapper
public interface UserMapper {
    NewUserDto userToNewUserDto(User user);
    /**
     * Update existing DTO
     */
    void updateDeliveryAddressFromAddress(SubUser subUser, @MappingTarget NewUserDto newUserDto);
}

4.7 Map mapping

@MapMapping(valueDateFormat = "yyyy-MM-dd HH:mm:ss")
Map
DateMapToStringMap(Map
sourceMap);

4.8 Nested mapping

Define separate mapper methods for nested objects; MapStruct will compose them automatically.

5. Obtaining a Mapper Instance

5.1 Mapper factory

Use Mappers.getMapper(YourMapper.class) or expose a static INSTANCE field inside the interface.

5.2 Dependency injection

Configure the mapper with @Mapper(componentModel = "spring") (or "cdi") to let the DI container inject it.

5.3 Injection strategy

Choose FIELD (default) or CONSTRUCTOR injection via injectionStrategy = InjectionStrategy.CONSTRUCTOR .

5.4 Custom type conversion

Implement a helper class (e.g., BooleanStrFormat) and reference it with @Mapper(uses = {BooleanStrFormat.class}) to convert Boolean ↔ String.

Overall, MapStruct provides a concise, performant, and type‑safe way to handle Java bean conversions, reducing boilerplate and improving maintainability.

javaPerformancecode generationMapStructAnnotation ProcessorBean Mapping
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.