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

lifecycle-customization.mddocs/

Lifecycle and Customization

Hooks and customization mechanisms for extending mapper behavior with custom logic, object factories, and lifecycle callbacks.

Capabilities

Before and After Mapping Hooks

Lifecycle hooks that allow custom logic execution before and after mapping operations.

/**
 * Marks a method to be invoked at the end of a generated mapping method.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface BeforeMapping {
}

/**
 * Marks a method to be invoked at the end of a generated mapping method.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface AfterMapping {
}

Usage Examples:

@Mapper
public abstract class UserMapper {
    // Abstract mapping method
    public abstract UserDto toUserDto(User user);
    
    // Before mapping hook
    @BeforeMapping
    protected void validateUser(User user) {
        if (user.getEmail() == null) {
            throw new IllegalArgumentException("User email cannot be null");
        }
    }
    
    // After mapping hook
    @AfterMapping
    protected void enrichUserDto(User user, @MappingTarget UserDto userDto) {
        // Add computed fields
        userDto.setDisplayName(user.getFirstName() + " " + user.getLastName());
        userDto.setAccountAge(calculateAccountAge(user.getCreatedDate()));
        
        // Apply business logic
        if (user.isPremiumMember()) {
            userDto.setBadge("PREMIUM");
        }
    }
    
    private int calculateAccountAge(LocalDate createdDate) {
        return Period.between(createdDate, LocalDate.now()).getYears();
    }
}

Object Factory Methods

Custom object creation for target instances instead of using default constructors.

/**
 * Marks a method as factory method for creating instances of the return type.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface ObjectFactory {
}

Usage Examples:

@Mapper
public abstract class OrderMapper {
    // Abstract mapping method
    public abstract OrderDto toOrderDto(Order order);
    
    // Object factory for creating OrderDto instances
    @ObjectFactory
    protected OrderDto createOrderDto(Order order) {
        // Custom creation logic based on source
        if (order.getType() == OrderType.EXPRESS) {
            return new ExpressOrderDto();
        } else if (order.getType() == OrderType.BULK) {
            return new BulkOrderDto();
        } else {
            return new StandardOrderDto();
        }
    }
    
    // Factory with qualifier
    @ObjectFactory
    @Named("createAuditableDto")
    protected AuditableDto createAuditableDto() {
        AuditableDto dto = new AuditableDto();
        dto.setCreatedAt(Instant.now());
        dto.setCreatedBy(getCurrentUser());
        return dto;
    }
    
    // Using qualified factory
    @Mapping(target = ".", qualifiedByName = "createAuditableDto")
    AuditableDto toAuditableDto(Entity entity);
    
    private String getCurrentUser() {
        // Implementation to get current user
        return SecurityContext.getCurrentUser();
    }
}

// Example with dependency injection
@Mapper(componentModel = "spring")
public abstract class SpringOrderMapper {
    @Autowired
    private OrderFactory orderFactory;
    
    public abstract OrderDto toOrderDto(Order order);
    
    @ObjectFactory
    protected OrderDto createOrderDto(Order order) {
        return orderFactory.createDto(order.getType());
    }
}

Decorator Pattern Support

Allows decoration of generated mappers with additional custom logic.

/**
 * Marks a mapper to be decorated with the class specified via DecoratedWith.value().
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@interface DecoratedWith {
    /** Decorator class */
    Class<?> value();
}

Usage Examples:

// Base mapper interface
@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
    PersonDto toPersonDto(Person person);
    
    List<PersonDto> toPersonDtos(List<Person> persons);
}

// Decorator class
public abstract class PersonMapperDecorator implements PersonMapper {
    private final PersonMapper delegate;
    
    public PersonMapperDecorator(PersonMapper delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public PersonDto toPersonDto(Person person) {
        // Call the generated mapper
        PersonDto dto = delegate.toPersonDto(person);
        
        // Add custom logic
        if (person.getAge() >= 18) {
            dto.setLegalStatus("ADULT");
        } else {
            dto.setLegalStatus("MINOR");
        }
        
        // Add external service integration
        dto.setCreditScore(creditScoreService.getScore(person.getSsn()));
        
        return dto;
    }
    
    @Override
    public List<PersonDto> toPersonDtos(List<Person> persons) {
        // Custom batch processing logic
        List<PersonDto> dtos = delegate.toPersonDtos(persons);
        
        // Batch enrich with external data
        enrichWithExternalData(dtos);
        
        return dtos;
    }
    
    private void enrichWithExternalData(List<PersonDto> dtos) {
        // Implementation for batch enrichment
    }
}

// Spring integration example
@Component
public abstract class SpringPersonMapperDecorator implements PersonMapper {
    @Autowired
    private PersonMapper delegate;
    
    @Autowired
    private ExternalService externalService;
    
    @Override
    public PersonDto toPersonDto(Person person) {
        PersonDto dto = delegate.toPersonDto(person);
        dto.setExternalData(externalService.getData(person.getId()));
        return dto;
    }
}

Parameter Annotations

Annotations for marking method parameters with special roles in mapping operations.

/**
 * Marks a parameter of a mapping method as target for the mapping.
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
@interface MappingTarget {
}

/**
 * Marks a parameter as mapping context.
 */
@Target(ElementType.PARAMETER) 
@Retention(RetentionPolicy.CLASS)
@interface Context {
}

/**
 * Provides the target type to select a mapping method.
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS) 
@interface TargetType {
}

/**
 * Provides the source property name available to the annotated parameter.
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
@interface SourcePropertyName {
}

/**
 * Provides the target property name available to the annotated parameter.
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
@interface TargetPropertyName {
}

Usage Examples:

@Mapper
public interface ParameterMapper {
    // Update mapping using @MappingTarget
    void updatePersonDto(Person person, @MappingTarget PersonDto dto);
    
    // Mapping with context
    PersonDto toPersonDto(Person person, @Context MappingContext context);
    
    // Method using context in custom logic
    default String formatName(String firstName, String lastName, @Context MappingContext context) {
        if (context.getLocale().getLanguage().equals("ja")) {
            return lastName + " " + firstName;
        } else {
            return firstName + " " + lastName;
        }
    }
    
    // Generic mapping with target type selection
    <T> T mapToType(Object source, @TargetType Class<T> targetType);
    
    // Custom property mapping methods
    default String customPropertyMapper(
        String value,
        @SourcePropertyName String sourceProperty,
        @TargetPropertyName String targetProperty
    ) {
        return String.format("[%s->%s]: %s", sourceProperty, targetProperty, value);
    }
}

// Context class example
public class MappingContext {
    private Locale locale;
    private String currentUser;
    private Map<String, Object> attributes;
    
    // constructors, getters, setters...
    
    public Locale getLocale() { return locale; }
    public String getCurrentUser() { return currentUser; }
    public Map<String, Object> getAttributes() { return attributes; }
}

// Usage with context
MappingContext context = new MappingContext();
context.setLocale(Locale.JAPANESE);
context.setCurrentUser("admin");

PersonDto dto = mapper.toPersonDto(person, context);

Conditional Mapping

Conditional mapping capabilities for controlling when mappings should be applied.

/**
 * Marks a method as condition check for source property.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface Condition {
}

/**
 * Marks a parameter to be used for source condition checking.
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
@interface SourceParameterCondition {
}

Usage Examples:

@Mapper
public abstract class ConditionalMapper {
    // Main mapping method
    @Mapping(target = "email", conditionQualifiedByName = "isValidEmail")
    @Mapping(target = "phone", conditionQualifiedByName = "hasValidPhone")
    public abstract ContactDto toContactDto(Person person);
    
    // Condition methods
    @Condition
    @Named("isValidEmail")
    protected boolean isValidEmail(String email) {
        return email != null && email.contains("@") && email.contains(".");
    }
    
    @Condition  
    @Named("hasValidPhone")
    protected boolean hasValidPhone(Person person) {
        return person.getPhone() != null && person.getPhone().matches("\\d{10}");
    }
    
    // Source parameter condition
    @Condition
    protected boolean shouldMapField(@SourceParameterCondition Person person, String value) {
        return person.isActive() && value != null;
    }
    
    // Using expression-based conditions
    @Mapping(
        target = "displayName", 
        source = "name",
        conditionExpression = "java(person.getName().length() > 2)"
    )
    @Mapping(
        target = "age",
        source = "birthDate", 
        conditionExpression = "java(person.getBirthDate() != null)"
    )
    public abstract ProfileDto toProfileDto(Person person);
}

Custom Mapper Methods

Defining custom mapping methods for specific transformations.

Usage Examples:

@Mapper
public abstract class CustomMethodMapper {
    // Abstract mapping method
    public abstract ProductDto toProductDto(Product product);
    
    // Custom mapping for specific field types
    protected String formatPrice(BigDecimal price) {
        if (price == null) return "N/A";
        return NumberFormat.getCurrencyInstance().format(price);
    }
    
    protected LocalDate stringToDate(String dateString) {
        if (dateString == null || dateString.trim().isEmpty()) {
            return null;
        }
        return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE);
    }
    
    protected String dateToString(LocalDate date) {
        return date != null ? date.format(DateTimeFormatter.ISO_LOCAL_DATE) : null;
    }
    
    // Qualified custom methods
    @Named("toUpperCase")
    protected String toUpperCase(String value) {
        return value != null ? value.toUpperCase() : null;
    }
    
    // Using custom methods in mappings
    @Mapping(target = "formattedPrice", source = "price")
    @Mapping(target = "name", source = "title", qualifiedByName = "toUpperCase")
    public abstract ProductSummaryDto toProductSummary(Product product);
}

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