Java annotation processor for the generation of type-safe bean mappers
—
Advanced mapping control mechanisms, conditional mapping, subclass mapping, and fine-grained control over the mapping process.
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);
}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
}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);
}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);
}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);
}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;
}
}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);
}
}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 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