CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-mapstruct--mapstruct

Java annotation processor for the generation of type-safe bean mappers

Pending
Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Advanced mapping control mechanisms, conditional mapping, subclass mapping, and fine-grained control over the mapping process.

Capabilities

Subclass Mapping

Support for polymorphic mapping with automatic subclass detection and specialized mappings.

/**
 * Configures the mapping to handle inheritance hierarchies.
 */
@Repeatable(SubclassMappings.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface SubclassMapping {
    /** Source subclass */
    Class<?> source();
    
    /** Target subclass */
    Class<?> target();
    
    /** Qualifiers for subclass mapping method selection */
    Class<? extends Annotation>[] qualifiedBy() default {};
    
    /** String-based qualifiers for subclass mapping method selection */
    String[] qualifiedByName() default {};
}

/**
 * Container for multiple @SubclassMapping annotations.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface SubclassMappings {
    SubclassMapping[] value();
}

Usage Examples:

// Inheritance hierarchy
public abstract class Vehicle {
    private String brand;
    private String model;
    // getters, setters...
}

public class Car extends Vehicle {
    private int doors;
    private boolean convertible;
    // getters, setters...
}

public class Motorcycle extends Vehicle {
    private boolean hasSidecar;
    private int engineSize;
    // getters, setters...
}

// DTO hierarchy
public abstract class VehicleDto {
    private String brand;
    private String model;
    // getters, setters...
}

public class CarDto extends VehicleDto {
    private int doorCount;
    private boolean isConvertible;
    // getters, setters...
}

public class MotorcycleDto extends VehicleDto {
    private boolean hasSidecar;
    private String engineCapacity;
    // getters, setters...
}

@Mapper
public interface VehicleMapper {
    // Polymorphic mapping with subclass mappings
    @SubclassMapping(source = Car.class, target = CarDto.class)
    @SubclassMapping(source = Motorcycle.class, target = MotorcycleDto.class)
    VehicleDto toVehicleDto(Vehicle vehicle);
    
    // Specific subclass mappings
    @Mapping(source = "doors", target = "doorCount")
    @Mapping(source = "convertible", target = "isConvertible")
    CarDto toCarDto(Car car);
    
    @Mapping(source = "engineSize", target = "engineCapacity", numberFormat = "#,### cc")
    MotorcycleDto toMotorcycleDto(Motorcycle motorcycle);
    
    // Collection with subclass mapping
    @SubclassMapping(source = Car.class, target = CarDto.class)
    @SubclassMapping(source = Motorcycle.class, target = MotorcycleDto.class)
    List<VehicleDto> toVehicleDtos(List<Vehicle> vehicles);
}

Subclass Exhaustive Strategy

Strategy for handling missing subclass mappings.

/**
 * Strategy for handling missing implementation for super classes when using SubclassMapping.
 */
enum SubclassExhaustiveStrategy {
    /** Generate compile error for missing subclass mappings */
    COMPILE_ERROR,
    
    /** Throw runtime exception for unmapped subclasses */
    RUNTIME_EXCEPTION
}

Usage Examples:

@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.COMPILE_ERROR)
public interface StrictVehicleMapper {
    // Compiler will enforce all subclasses are mapped
    @SubclassMapping(source = Car.class, target = CarDto.class)
    @SubclassMapping(source = Motorcycle.class, target = MotorcycleDto.class)
    // Missing Truck.class mapping will cause compile error
    VehicleDto toVehicleDto(Vehicle vehicle);
    
    CarDto toCarDto(Car car);
    MotorcycleDto toMotorcycleDto(Motorcycle motorcycle);
    // TruckDto toTruckDto(Truck truck); // Must be implemented
}

@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION)
public interface RuntimeVehicleMapper {
    // Missing subclass mappings will throw runtime exception
    @SubclassMapping(source = Car.class, target = CarDto.class)
    VehicleDto toVehicleDto(Vehicle vehicle);
    
    CarDto toCarDto(Car car);
    // Mapping Motorcycle without SubclassMapping will throw exception at runtime
}

Mapping Control Annotations

Fine-grained control over which mapping mechanisms are allowed.

/**
 * Meta-annotation to mark an annotation as a mapping control annotation.
 */
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.CLASS)
@interface MappingControl {
    /** Allowed mapping mechanisms */
    MappingControl[] value() default {};
}

/**
 * Enum defining the types of mapping mechanisms.
 */
enum MappingControl {
    /** Direct assignment (no conversion) */
    DIRECT,
    
    /** Built-in type conversions */
    BUILT_IN_CONVERSION,
    
    /** User-defined mapping methods */
    MAPPING_METHOD,
    
    /** Complex mappings (multi-step conversions) */
    COMPLEX_MAPPING
}

Predefined Control Annotations:

/**
 * Forces deep cloning by only allowing mapping methods.
 */
@MappingControl(MappingControl.MAPPING_METHOD)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
@interface DeepClone {
}

/**
 * Disables complex mappings (multi-step transformations).
 */
@MappingControl({MappingControl.DIRECT, MappingControl.BUILT_IN_CONVERSION, MappingControl.MAPPING_METHOD})
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
@interface NoComplexMapping {
}

/**
 * Container for multiple @MappingControl annotations.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
@interface MappingControls {
    Class<? extends Annotation>[] value();
}

Usage Examples:

// Deep cloning mapper - forces all mappings through methods
@Mapper
@DeepClone
public interface DeepCloneMapper {
    PersonDto toPersonDto(Person person);
    
    // All nested objects will be deeply cloned through mapping methods
    OrderDto toOrderDto(Order order);
    
    // Custom deep cloning method
    AddressDto cloneAddress(Address address);
}

// No complex mapping - prevents multi-step conversions
@Mapper
public interface SimpleMapper {
    @NoComplexMapping
    @Mapping(source = "name", target = "fullName")
    UserDto toUserDto(User user);
    
    // Only direct assignments and built-in conversions allowed
    ProductDto toProductDto(Product product);
}

// Custom mapping control
@MappingControl({MappingControl.DIRECT, MappingControl.MAPPING_METHOD})
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface DirectOrCustomOnly {
}

@Mapper
public interface ControlledMapper {
    @DirectOrCustomOnly
    @Mapping(source = "value", target = "result")
    ResultDto toResultDto(Source source);
    
    // Custom method for controlled mapping
    String transformValue(String input);
}

Builder Pattern Support

Configuration for builder pattern integration in target objects.

/**
 * Configuration for builder pattern support.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
@interface Builder {
    /** Name of the build method */
    String buildMethod() default "build";
    
    /** Disable builder pattern */
    boolean disableBuilder() default false;
}

Usage Examples:

// Target class with builder
public class PersonDto {
    private String name;
    private int age;
    private String email;
    
    // Private constructor
    private PersonDto(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder {
        private String name;
        private int age;
        private String email;
        
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public Builder email(String email) {
            this.email = email;
            return this;
        }
        
        public PersonDto build() {
            return new PersonDto(this);
        }
    }
    
    // getters...
}

@Mapper
public interface BuilderMapper {
    // Automatically uses builder pattern
    PersonDto toPersonDto(Person person);
    
    // Custom builder configuration
    @Builder(buildMethod = "create")
    CustomDto toCustomDto(Entity entity);
    
    // Disable builder for specific mapping
    @Builder(disableBuilder = true)
    SimpleDto toSimpleDto(SimpleEntity entity);
}

Annotation Processing Control

Control over generated code annotations and metadata.

// Mapper configuration affecting generated code
@Mapper(
    suppressTimestampInGenerated = true,  // Remove timestamp from @Generated
    implementationName = "Custom<CLASS_NAME>Impl",  // Custom implementation name
    implementationPackage = "com.example.generated"  // Custom package
)
public interface ProcessingControlMapper {
    PersonDto toPersonDto(Person person);
}

Experimental Features

Access to experimental features that may change in future versions.

/**
 * Marks features as experimental and subject to change.
 */
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
@interface Experimental {
}

Usage Examples:

@Mapper
public interface ExperimentalMapper {
    @Experimental
    @Mapping(target = "newField", expression = "java(experimentalTransform(source.getValue()))")
    ExperimentalDto toExperimentalDto(Source source);
    
    @Experimental
    default String experimentalTransform(String input) {
        // Experimental transformation logic
        return input != null ? input.transform(String::toUpperCase) : null;
    }
}

Advanced Error Handling

Custom exception handling for mapping operations.

Usage Examples:

@Mapper(unexpectedValueMappingException = MappingException.class)
public interface ErrorHandlingMapper {
    // Custom exception for enum mapping errors
    StatusDto mapStatus(Status status);
    
    // Method-level exception override
    @BeanMapping(unexpectedValueMappingException = ValidationException.class)
    ValidatedDto toValidatedDto(Entity entity);
}

// Custom exceptions
public class MappingException extends RuntimeException {
    public MappingException(String message) {
        super(message);
    }
}

public class ValidationException extends Exception {
    public ValidationException(String message, Throwable cause) {
        super(message, cause);
    }
}

Performance Optimizations

Advanced configuration for optimizing generated mapping code.

Usage Examples:

@Mapper(
    // Disable automatic sub-mapping generation for performance
    disableSubMappingMethodsGeneration = true,
    
    // Use constructor injection for better performance
    injectionStrategy = InjectionStrategy.CONSTRUCTOR,
    
    // Strict null checking strategy
    nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface PerformanceMapper {
    PersonDto toPersonDto(Person person);
    
    // Explicit sub-mapping methods for control
    AddressDto toAddressDto(Address address);
    
    PhoneDto toPhoneDto(Phone phone);
}

Integration with Validation Frameworks

Integration patterns with validation frameworks.

Usage Examples:

@Mapper
public abstract class ValidatingMapper {
    @Autowired
    private Validator validator;
    
    public PersonDto toPersonDto(Person person) {
        PersonDto dto = mapPersonDto(person);
        
        // Validate the result
        Set<ConstraintViolation<PersonDto>> violations = validator.validate(dto);
        if (!violations.isEmpty()) {
            throw new ValidationException("Mapping validation failed: " + violations);
        }
        
        return dto;
    }
    
    @Mapping(source = "firstName", target = "name")
    @Mapping(source = "birthDate", target = "dateOfBirth", dateFormat = "yyyy-MM-dd")
    protected abstract PersonDto mapPersonDto(Person person);
    
    @AfterMapping
    protected void validateResult(@MappingTarget PersonDto dto) {
        if (dto.getName() == null || dto.getName().trim().isEmpty()) {
            throw new IllegalStateException("Name cannot be empty after mapping");
        }
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-mapstruct--mapstruct

docs

advanced-features.md

collection-mapping.md

configuration-inheritance.md

core-mapping.md

enum-mapping.md

index.md

lifecycle-customization.md

tile.json