@Mapping, @AfterMapping, and @MappingTarget in Action
🧩 Introduction
In modern Java applications, especially with frameworks like Spring Boot, object mapping is a common task. Whether you’re transforming DTOs into entities or mapping between domain layers, doing this manually is tedious and error-prone.
MapStruct is a compile-time code generator that simplifies this task—fast, type-safe, and easy to use. In this article, we’ll explore three powerful annotations that give you more control and flexibility:
@Mapping
@AfterMapping
@MappingTarget
🔧 Setting the Stage: Basic Setup
Let’s assume we have a simple Spring Boot project with MapStruct configured.
Dependencies (Maven):
<properties>
<java.version>21</java.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<mvn.compile.version>3.11.0</mvn.compile.version>
</properties>
<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>provided</scope>
</dependency>
<!-- and in build tag plugins -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${mvn.compile.version}</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<!-- Other processors like Lombok can go here -->
</annotationProcessorPaths>
</configuration>
</plugin>
Make sure annotation processing is enabled in your IDE or build tool.
✅ Example Scenario: Mapping User DTO to Entity
Let’s say you have:
UserDTO.java
public record UserDTO(String firstName, String lastName, String email) {}
User.java
public class User {
private String fullName;
private String email;
private LocalDateTime createdAt;
// getters & setters
}
🎯 Using @Mapping
to Customize Field Mapping
By default, MapStruct matches fields by name. But when the names don’t match (like firstName
+ lastName
→ fullName
), you can use @Mapping
.
UserMapper.java
@Mapper
public interface UserMapper {
@Mapping(target = "fullName", expression = "java(dto.firstName() + \" \" + dto.lastName())")
@Mapping(target = "createdAt", ignore = true) // we’ll handle this later
User toEntity(UserDTO dto);
}
Here:
expression = "java(...)”
lets you use Java code directly for custom logic.ignore = true
tells MapStruct to skip mapping that field.
🧪 Enhancing Mapping with @AfterMapping
Let’s say we want to set createdAt
to the current time. This is where @AfterMapping
comes in handy.
@Mapper
public interface UserMapper {
@Mapping(target = "fullName", expression = "java(dto.firstName() + \" \" + dto.lastName())")
@Mapping(target = "createdAt", ignore = true)
User toEntity(UserDTO dto);
@AfterMapping
default void setCreatedAt(@MappingTarget User user) {
user.setCreatedAt(LocalDateTime.now());
}
}
@AfterMapping
lets you customize the result after the auto-mapping is done.@MappingTarget
gives you access to the mapped target object.
🔁 Reusing Objects with @MappingTarget
Suppose you’re updating an existing User
entity instead of creating a new one:
@Mapper
public interface UserMapper {
@Mapping(target = "fullName", expression = "java(dto.firstName() + \" \" + dto.lastName())")
@Mapping(target = "createdAt", ignore = true)
void updateEntity(UserDTO dto, @MappingTarget User user);
}
- This avoids creating a new object.
- Great for PATCH/update endpoints where you fetch an entity from the DB, then update some fields.
🧠 Wrap-Up
These three annotations are the foundation of writing powerful, flexible, and clean mappers with MapStruct:
Annotation | Purpose |
---|---|
@Mapping | Customize individual field mapping |
@AfterMapping | Add post-processing logic |
@MappingTarget | Modify an existing object instead of creating a new one |
Mastering these tools helps you avoid boilerplate, improve readability, and maintain a clean domain model.
📦 Bonus: When Should You Use These?
- Use
@Mapping
when field names/types differ or when you want custom logic. - Use
@AfterMapping
for audit fields (createdAt
,updatedBy
) or conditional logic. - Use
@MappingTarget
when updating existing entities (e.g., in services).