Tag: mapstruct

  • Mastering MapStruct

    Mastering MapStruct

    @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 + lastNamefullName), 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:

    AnnotationPurpose
    @MappingCustomize individual field mapping
    @AfterMappingAdd post-processing logic
    @MappingTargetModify 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).