The interface is localized. Skill descriptions and documents are machine-translated by AI into your language; code, commands, paths and technical terms stay in the original, and the author's document governs.
Explore skills
Development · SKILL.md

mapstruct

mapstruct

MapStruct Java mapping framework. Covers basic mappers, update mappings, nested objects, collections, custom methods, and Lombok integration. Based on castellino and gestionale-presenze production patterns. USE WHEN: user mentions "mapstruct", "@Mapper", "@Mapping", asks about "DTO mapping", "entity conversion", "@MappingTarget", "nested mapping" DO NOT U…

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill documentation

GitHub

name: mapstruct description: | MapStruct Java mapping framework. Covers basic mappers, update mappings, nested objects, collections, custom methods, and Lombok integration. Based on castellino and gestionale-presenze production patterns.

USE WHEN: user mentions "mapstruct", "@Mapper", "@Mapping", asks about "DTO mapping", "entity conversion", "@MappingTarget", "nested mapping"

DO NOT USE FOR: Java language - use java skill instead DO NOT USE FOR: Lombok - use lombok skill instead DO NOT USE FOR: JPA entities - use JPA-specific skills allowed-tools: Read, Grep, Glob, Write, Edit

MapStruct Object Mapping

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: mapstruct for comprehensive documentation.

Basic Mapper

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    UserResponse toResponse(User user);

    List<UserResponse> toResponseList(List<User> users);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    @Mapping(target = "updatedAt", ignore = true)
    @Mapping(target = "password", ignore = true)
    User toEntity(CreateUserRequest dto);
}

Update Mapping

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    // Partial update - ignores null values
    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateEntity(UpdateUserRequest dto, @MappingTarget User user);

    // Full update - sets all fields including nulls
    void updateEntityFull(UpdateUserRequest dto, @MappingTarget User user);
}

Field Mapping

@Mapper(componentModel = "spring")
public interface OrderMapper {

    // Different field names
    @Mapping(source = "customer.name", target = "customerName")
    @Mapping(source = "customer.email", target = "customerEmail")
    @Mapping(source = "items", target = "orderItems")
    OrderResponse toResponse(Order order);

    // Constant values
    @Mapping(target = "status", constant = "PENDING")
    @Mapping(target = "createdAt", expression = "java(java.time.LocalDateTime.now())")
    Order toEntity(CreateOrderRequest dto);

    // Expression
    @Mapping(target = "fullName", expression = "java(user.getFirstName() + \" \" + user.getLastName())")
    UserResponse toResponse(User user);

    // Date formatting
    @Mapping(source = "createdAt", target = "createdDate", dateFormat = "yyyy-MM-dd")
    OrderResponse toResponse(Order order);
}

Nested Objects

@Mapper(componentModel = "spring", uses = {AddressMapper.class, ItemMapper.class})
public interface OrderMapper {

    // Uses AddressMapper for address field
    // Uses ItemMapper for items collection
    OrderResponse toResponse(Order order);
}

@Mapper(componentModel = "spring")
public interface AddressMapper {
    AddressResponse toResponse(Address address);
}

@Mapper(componentModel = "spring")
public interface ItemMapper {
    ItemResponse toResponse(Item item);
}

Enum Mapping

@Mapper(componentModel = "spring")
public interface StatusMapper {

    @ValueMappings({
        @ValueMapping(source = "ACTIVE", target = "ENABLED"),
        @ValueMapping(source = "INACTIVE", target = "DISABLED"),
        @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "UNKNOWN")
    })
    ExternalStatus toExternalStatus(InternalStatus status);
}

Custom Methods

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @Autowired
    protected RoleRepository roleRepository;

    public abstract UserResponse toResponse(User user);

    @Mapping(target = "roles", source = "roleIds")
    public abstract User toEntity(CreateUserRequest dto);

    // Custom mapping method
    protected Set<Role> mapRoles(Set<Long> roleIds) {
        if (roleIds == null) return new HashSet<>();
        return roleIds.stream()
            .map(id -> roleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Role", "id", id)))
            .collect(Collectors.toSet());
    }
}

Collection Mapping

@Mapper(componentModel = "spring")
public interface ProductMapper {

    ProductResponse toResponse(Product product);

    List<ProductResponse> toResponseList(List<Product> products);

    Set<ProductResponse> toResponseSet(Set<Product> products);

    // Page mapping
    default Page<ProductResponse> toResponsePage(Page<Product> products) {
        return products.map(this::toResponse);
    }
}

After/Before Mapping

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @AfterMapping
    protected void afterMapping(@MappingTarget UserResponse response, User user) {
        response.setDisplayName(user.getFirstName() + " " + user.getLastName().charAt(0) + ".");
    }

    @BeforeMapping
    protected void beforeMapping(CreateUserRequest dto) {
        if (dto.getEmail() != null) {
            dto.setEmail(dto.getEmail().toLowerCase().trim());
        }
    }
}

Conditional Mapping

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @Condition
    public boolean isNotEmpty(String value) {
        return value != null && !value.trim().isEmpty();
    }

    // Only maps non-empty strings
    UserResponse toResponse(User user);
}

Maven Configuration

<properties>
    <mapstruct.version>1.6.2</mapstruct.version>
    <lombok.version>1.18.30</lombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.13.0</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <annotationProcessorPaths>
                    <!-- Order matters: Lombok first -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${mapstruct.version}</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                </annotationProcessorPaths>
                <compilerArgs>
                    <arg>-Amapstruct.defaultComponentModel=spring</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

Key Annotations

AnnotationPurpose
@MapperDefine mapper interface/abstract class
@MappingField-level mapping configuration
@MappingTargetUpdate existing object
@BeanMappingBean-level mapping settings
@AfterMappingPost-processing method
@BeforeMappingPre-processing method
@ConditionConditional mapping
@ValueMappingEnum value mapping

Best Practices

  1. Use componentModel = "spring" for Spring injection
  2. Use unmappedTargetPolicy = ReportingPolicy.IGNORE for DTOs with fewer fields
  3. Always exclude id, createdAt, updatedAt when mapping from DTOs
  4. Use @MappingTarget for partial updates
  5. Order annotation processors: Lombok → MapStruct → Binding
  6. Use abstract classes instead of interfaces for custom logic
  7. Use @Condition for conditional mapping logic

When NOT to Use This Skill

ScenarioUse Instead
Java language featuresjava skill
Lombok annotationslombok skill
Spring configurationbackend-spring-boot skill
JPA entity operationsJPA-specific skills
Simple copyingManual mapping or BeanUtils

Anti-Patterns

Anti-PatternWhy It's BadCorrect Approach
Mapping entities to entitiesBreaks change trackingMap DTO to entity
Not using @MappingTargetInefficient updatesUse for partial updates
Complex logic in expressionsHard to testUse custom methods
Ignoring all unmappedMisses fields silentlyUse WARN or ERROR policy
Not excluding audit fieldsOverwrites metadataExclude id, timestamps
Circular referencesStackOverflowErrorBreak cycles with custom mapping
Using interfaces for custom logicCan't inject dependenciesUse abstract classes
Not testing mappersRuntime mapping errorsWrite mapper tests

Quick Troubleshooting

IssueCauseSolution
"Cannot find implementation"Annotation processing failedCheck processor configuration
Lombok fields not foundWrong processor orderLombok before MapStruct
Mapper not autowiredWrong componentModelUse componentModel = "spring"
Circular dependencyMappers reference each otherUse @Lazy or refactor
UnmappedTargetProperty warningMissing mappingAdd @Mapping or ignore policy
NullPointerException in mappingNull sourceAdd null checks or NullValuePropertyMappingStrategy
Custom method not calledWrong signatureMatch method parameters exactly
Generated code not updatedIDE cacheClean and rebuild project

Reference Documentation

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: mapstruct for comprehensive documentation.

Saved skills