Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
MapStruct
https://github.com/mapstruct/mapstruct
Admin
MapStruct is a Java annotation processor that generates type-safe and high-performance mappers for
...
Tokens:
8,630
Snippets:
74
Trust Score:
7
Update:
1 day ago
Context
Skills
Chat
Benchmark
92.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# MapStruct - Java Bean Mappings, The Easy Way MapStruct is a Java annotation processor that generates type-safe and high-performance mappers for Java bean classes, including support for Java 16+ records. By automating the creation of mappings at compile time, MapStruct eliminates the need for tedious and error-prone manual coding while providing fast execution through plain method invocations instead of reflection. The generated code handles null checks, type conversions, and nested object mappings automatically. Compared to runtime mapping frameworks, MapStruct offers compile-time type safety (preventing accidental mappings between unrelated types), clear error reports at build time for incomplete or incorrect mappings, self-contained code with no runtime dependencies, and easily debuggable generated code. The framework follows a convention-over-configuration approach, using sensible defaults while allowing custom configurations when needed. ## Setup ### Maven Configuration ```xml <properties> <org.mapstruct.version>1.6.3</org.mapstruct.version> </properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.14.1</version> <configuration> <source>17</source> <target>17</target> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build> ``` ### Gradle Configuration ```groovy plugins { id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse } dependencies { implementation "org.mapstruct:mapstruct:1.6.3" annotationProcessor "org.mapstruct:mapstruct-processor:1.6.3" testAnnotationProcessor "org.mapstruct:mapstruct-processor:1.6.3" } ``` ## @Mapper - Basic Mapper Definition The `@Mapper` annotation marks an interface or abstract class as a mapper, causing MapStruct to generate an implementation during compilation. Properties with the same name are mapped automatically, while `@Mapping` annotations handle name differences and custom transformations. ```java // Source and target classes public class Car { private String make; private int numberOfSeats; private Person driver; // getters and setters } public class CarDto { private String manufacturer; private int seatCount; private PersonDto driver; // getters and setters } // Mapper interface @Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); @Mapping(target = "manufacturer", source = "make") @Mapping(target = "seatCount", source = "numberOfSeats") CarDto carToCarDto(Car car); PersonDto personToPersonDto(Person person); } // Usage Car car = new Car(); car.setMake("Toyota"); car.setNumberOfSeats(5); car.setDriver(new Person("John", "Doe")); CarDto dto = CarMapper.INSTANCE.carToCarDto(car); // dto.getManufacturer() returns "Toyota" // dto.getSeatCount() returns 5 // dto.getDriver() is automatically mapped using personToPersonDto() ``` ## @Mapping - Property Mapping Customization The `@Mapping` annotation customizes how individual properties are mapped, supporting source/target name mapping, constants, expressions, default values, and nested property access using dot notation. ```java @Mapper public interface OrderMapper { OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class); // Basic property mapping with different names @Mapping(target = "customerName", source = "client.fullName") // Constant value (always set regardless of source) @Mapping(target = "status", constant = "NEW") // Default value (used when source is null) @Mapping(target = "priority", source = "urgency", defaultValue = "NORMAL") // Java expression for complex logic @Mapping(target = "orderDate", expression = "java(java.time.LocalDate.now())") // Default expression (expression used when source is null) @Mapping(target = "orderId", source = "id", defaultExpression = "java(java.util.UUID.randomUUID().toString())") // Ignore a property @Mapping(target = "internalCode", ignore = true) // Date formatting @Mapping(target = "createdAt", source = "timestamp", dateFormat = "yyyy-MM-dd HH:mm:ss") // Number formatting @Mapping(target = "formattedPrice", source = "price", numberFormat = "$#.00") OrderDto orderToOrderDto(Order order); } // Nested property mapping - mapping from nested source to flat target @Mapper public interface AddressMapper { @Mapping(target = "street", source = "address.streetName") @Mapping(target = "city", source = "address.city") @Mapping(target = "zipCode", source = "address.postalCode") FlatAddressDto personToFlatAddress(Person person); } // Multiple source parameters @Mapper public interface DeliveryMapper { @Mapping(target = "recipientName", source = "person.name") @Mapping(target = "deliveryStreet", source = "address.street") @Mapping(target = "orderId", source = "orderId") DeliveryDto combine(Person person, Address address, String orderId); } ``` ## Component Models - Spring, CDI, and Dependency Injection MapStruct supports multiple component models for integration with dependency injection frameworks. The `componentModel` attribute controls how mapper instances are created and retrieved. ```java // Spring component model - generates @Component annotation @Mapper(componentModel = "spring") public interface CarMapper { CarDto carToCarDto(Car car); } // Usage in Spring @Service public class CarService { @Autowired private CarMapper carMapper; public CarDto convertCar(Car car) { return carMapper.carToCarDto(car); } } // CDI component model - generates @ApplicationScoped annotation @Mapper(componentModel = "cdi") public interface CarMapper { CarDto carToCarDto(Car car); } // Usage in CDI public class CarService { @Inject private CarMapper carMapper; } // JSR 330 component model - generates @Named annotation @Mapper(componentModel = "jsr330") public interface CarMapper { CarDto carToCarDto(Car car); } // Constructor injection (recommended for testing) @Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR, uses = EngineMapper.class) public interface CarMapper { CarDto carToCarDto(Car car); } // Global configuration via compiler options // -Amapstruct.defaultComponentModel=spring ``` ## @MappingTarget - Update Existing Objects The `@MappingTarget` annotation enables mapping to an existing target object instead of creating a new one. This is useful for update operations where you want to modify an existing entity with values from a DTO. ```java @Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); // Update method - modifies existing Car instance void updateCarFromDto(CarDto dto, @MappingTarget Car car); // Update method with return value for fluent API Car updateCarFromDtoAndReturn(CarDto dto, @MappingTarget Car car); @Mapping(target = "id", ignore = true) // Don't overwrite ID @Mapping(target = "createdAt", ignore = true) // Preserve creation date void updateEntityFromDto(CarDto dto, @MappingTarget Car car); } // Usage Car existingCar = carRepository.findById(1L); CarDto updateDto = new CarDto(); updateDto.setManufacturer("Honda"); updateDto.setSeatCount(4); CarMapper.INSTANCE.updateCarFromDto(updateDto, existingCar); // existingCar is now updated with values from updateDto ``` ## Collection and Map Mapping MapStruct automatically generates mapping methods for collections and maps, iterating through elements and applying the appropriate conversion for each item. ```java @Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); // List mapping - uses carToCarDto for each element List<CarDto> carsToCarDtos(List<Car> cars); // Set mapping with type conversion Set<String> integerSetToStringSet(Set<Integer> integers); // Single element mapping (required for collection mapping) CarDto carToCarDto(Car car); // Map mapping with format options @MapMapping(valueDateFormat = "dd.MM.yyyy") Map<String, String> dateMapToStringMap(Map<String, Date> source); // Iterable mapping with element customization @IterableMapping(dateFormat = "yyyy-MM-dd") List<String> datesToStrings(List<Date> dates); @IterableMapping(numberFormat = "$#.00") List<String> pricesToFormattedStrings(List<BigDecimal> prices); } // Generated code iterates and converts each element // List<CarDto> result = new ArrayList<>(); // for (Car car : cars) { // result.add(carToCarDto(car)); // } // return result; ``` ## Enum Mapping with @ValueMapping MapStruct supports mapping between enum types using `@ValueMapping` annotations. By default, constants with the same name are mapped automatically, while different names require explicit mapping. ```java public enum OrderType { RETAIL, B2B, EXTRA, STANDARD } public enum ExternalOrderType { RETAIL, B2B, SPECIAL, DEFAULT } @Mapper public interface OrderMapper { OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class); @ValueMappings({ @ValueMapping(target = "SPECIAL", source = "EXTRA"), @ValueMapping(target = "DEFAULT", source = "STANDARD") }) ExternalOrderType toExternal(OrderType orderType); // Map null to a specific value @ValueMappings({ @ValueMapping(source = MappingConstants.NULL, target = "DEFAULT"), @ValueMapping(source = "UNKNOWN", target = MappingConstants.NULL) }) ExternalOrderType toExternalWithNullHandling(OrderType orderType); // Map any remaining unmapped values to a default @ValueMappings({ @ValueMapping(source = "EXTRA", target = "SPECIAL"), @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT") }) ExternalOrderType toExternalWithDefault(OrderType orderType); // Custom name transformation (suffix/prefix/case) @EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE") CheeseTypeSuffixed map(CheeseType cheese); } ``` ## @BeforeMapping and @AfterMapping - Lifecycle Callbacks Callback methods annotated with `@BeforeMapping` and `@AfterMapping` allow custom logic to be executed before or after the mapping process. These methods can be defined in the mapper itself, in referenced classes via `uses`, or in `@Context` parameters. ```java @Mapper public abstract class VehicleMapper { // Called before mapping starts (before null check and object creation) @BeforeMapping protected void validateSource(Car car) { if (car.getVin() == null) { throw new IllegalArgumentException("VIN is required"); } } // Called after target is constructed but before return @AfterMapping protected void enrichDto(Car car, @MappingTarget CarDto dto) { dto.setFullDescription(car.getMake() + " " + car.getModel()); dto.setMappedAt(LocalDateTime.now()); } // With type parameters for generic handling @AfterMapping protected <T extends BaseDto> T setAuditFields(@MappingTarget T dto) { dto.setLastModified(LocalDateTime.now()); return dto; } public abstract CarDto toCarDto(Car car); } // Context-based callbacks @Mapper public interface CarMapper { CarDto toCarDto(Car car, @Context MappingContext context); } public class MappingContext { @BeforeMapping public void beforeMapping(Car car) { // Called before mapping } @AfterMapping public void afterMapping(@MappingTarget CarDto dto) { // Called after mapping } } ``` ## Using Other Mappers with @Mapper(uses) The `uses` attribute allows referencing other mapper classes or custom conversion classes. MapStruct will invoke methods from these classes when type conversions are needed. ```java // Custom date mapper class public class DateMapper { public String dateToString(Date date) { return date != null ? new SimpleDateFormat("yyyy-MM-dd").format(date) : null; } public Date stringToDate(String date) { try { return date != null ? new SimpleDateFormat("yyyy-MM-dd").parse(date) : null; } catch (ParseException e) { throw new RuntimeException(e); } } } // Mapper that uses the custom DateMapper @Mapper(uses = DateMapper.class) public interface OrderMapper { // MapStruct will use DateMapper.dateToString() for Date->String conversion OrderDto orderToOrderDto(Order order); } // Using multiple mappers @Mapper(uses = {DateMapper.class, AddressMapper.class, PersonMapper.class}) public interface CompanyMapper { CompanyDto companyToCompanyDto(Company company); } // Entity resolution mapper (e.g., for JPA) @ApplicationScoped public class ReferenceMapper { @PersistenceContext private EntityManager entityManager; public <T extends BaseEntity> T resolve(Long id, @TargetType Class<T> entityClass) { return id != null ? entityManager.find(entityClass, id) : null; } public Long toId(BaseEntity entity) { return entity != null ? entity.getId() : null; } } @Mapper(componentModel = "cdi", uses = ReferenceMapper.class) public interface OrderMapper { // Customer reference (Long) will be resolved to Customer entity Order orderDtoToOrder(OrderDto dto); } ``` ## @DecoratedWith - Customizing Generated Mappers Decorators allow customizing specific mapping methods while letting MapStruct generate the rest. The decorator is a subclass that can delegate to the original generated implementation. ```java @Mapper @DecoratedWith(PersonMapperDecorator.class) public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); PersonDto personToPersonDto(Person person); AddressDto addressToAddressDto(Address address); } // Decorator implementation public abstract class PersonMapperDecorator implements PersonMapper { private final PersonMapper delegate; public PersonMapperDecorator(PersonMapper delegate) { this.delegate = delegate; } @Override public PersonDto personToPersonDto(Person person) { PersonDto dto = delegate.personToPersonDto(person); // Add custom logic dto.setFullName(person.getFirstName() + " " + person.getLastName()); return dto; } // addressToAddressDto uses the generated implementation via delegate } // Spring-based decorator public abstract class PersonMapperDecorator implements PersonMapper { @Autowired @Qualifier("delegate") private PersonMapper delegate; @Override public PersonDto personToPersonDto(Person person) { PersonDto dto = delegate.personToPersonDto(person); dto.setFullName(person.getFirstName() + " " + person.getLastName()); return dto; } } ``` ## @InheritConfiguration and @InheritInverseConfiguration These annotations enable reusing mapping configurations between methods, reducing duplication. `@InheritConfiguration` copies mappings from another method, while `@InheritInverseConfiguration` automatically reverses source/target mappings. ```java @Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); @Mapping(target = "seatCount", source = "numberOfSeats") @Mapping(target = "manufacturer", source = "make") CarDto carToCarDto(Car car); // Inherits all mappings from carToCarDto and applies them to update method @InheritConfiguration(name = "carToCarDto") void updateCarDtoFromCar(Car car, @MappingTarget CarDto dto); // Automatically reverses the mappings: seatCount->numberOfSeats, manufacturer->make @InheritInverseConfiguration(name = "carToCarDto") Car carDtoToCar(CarDto dto); // Override specific mappings while inheriting others @InheritInverseConfiguration(name = "carToCarDto") @Mapping(target = "numberOfSeats", ignore = true) // Override: don't map this Car carDtoToCarPartial(CarDto dto); } ``` ## @MapperConfig - Shared Configuration `@MapperConfig` defines reusable configuration that can be shared across multiple mappers. This promotes consistency and reduces duplication in large projects. ```java // Central configuration @MapperConfig( componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG ) public interface CentralConfig { // Prototype methods for inheritance @Mapping(target = "id", ignore = true) @Mapping(target = "createdAt", ignore = true) @Mapping(target = "updatedAt", expression = "java(java.time.LocalDateTime.now())") BaseEntity anyDtoToEntity(BaseDto dto); } // Mapper using shared config @Mapper(config = CentralConfig.class) public interface UserMapper { // Automatically inherits mappings from CentralConfig.anyDtoToEntity() // if User extends BaseEntity and UserDto extends BaseDto User userDtoToUser(UserDto dto); } // Another mapper using same config @Mapper(config = CentralConfig.class, uses = AddressMapper.class) public interface CompanyMapper { Company companyDtoToCompany(CompanyDto dto); } ``` ## @Condition - Conditional Property Mapping The `@Condition` annotation enables custom presence checks that control whether a property should be mapped. This is useful for implementing validation logic beyond simple null checks. ```java @Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); CarDto carToCarDto(Car car); // Custom condition: only map non-empty strings @Condition default boolean isNotEmpty(String value) { return value != null && !value.trim().isEmpty(); } // Condition with property name access @Condition default boolean shouldMapProperty( String value, @TargetPropertyName String targetProperty, @SourcePropertyName String sourceProperty) { // Custom logic based on property names if ("password".equals(sourceProperty)) { return false; // Never map password } return value != null; } } // Source parameter condition @Mapper public interface OrderMapper { OrderDto orderToOrderDto(Order order); // Only map if order has a valid ID @SourceParameterCondition default boolean hasValidOrder(Order order) { return order != null && order.getId() != null; } } ``` ## Builder and Constructor Support MapStruct automatically detects and uses builders (Lombok, Immutables, AutoValue) or constructors for creating target objects. This enables mapping to immutable types without setters. ```java // Immutable target with builder public class PersonDto { private final String name; private final int age; private PersonDto(Builder builder) { this.name = builder.name; this.age = builder.age; } public static Builder builder() { return new Builder(); } public static class Builder { private String name; private int age; public Builder name(String name) { this.name = name; return this; } public Builder age(int age) { this.age = age; return this; } public PersonDto build() { return new PersonDto(this); } } } @Mapper public interface PersonMapper { // MapStruct automatically uses PersonDto.builder() PersonDto personToPersonDto(Person person); } // Constructor-based mapping public class PersonDto { private final String name; private final int age; public PersonDto(String name, int age) { this.name = name; this.age = age; } } @Mapper public interface PersonMapper { // MapStruct matches constructor parameters by name PersonDto personToPersonDto(Person person); } // Specify which constructor to use with @Default public class PersonDto { public PersonDto() { } @Default // MapStruct will use this constructor public PersonDto(String name, int age) { } } // Disable builder detection @Mapper(builder = @Builder(disableBuilder = true)) public interface PersonMapper { PersonDto personToPersonDto(Person person); } ``` ## @SubclassMapping - Polymorphic Mapping `@SubclassMapping` enables mapping polymorphic types by specifying how subclasses of the source type should be mapped to subclasses of the target type. ```java public abstract class Fruit { } public class Apple extends Fruit { private String variety; } public class Banana extends Fruit { private String origin; } public abstract class FruitDto { } public class AppleDto extends FruitDto { private String variety; } public class BananaDto extends FruitDto { private String origin; } @Mapper public interface FruitMapper { FruitMapper INSTANCE = Mappers.getMapper(FruitMapper.class); @SubclassMapping(source = Apple.class, target = AppleDto.class) @SubclassMapping(source = Banana.class, target = BananaDto.class) FruitDto toFruitDto(Fruit fruit); // Individual mappings (generated automatically if not defined) AppleDto toAppleDto(Apple apple); BananaDto toBananaDto(Banana banana); } // Usage Fruit apple = new Apple("Granny Smith"); FruitDto dto = FruitMapper.INSTANCE.toFruitDto(apple); // dto is an AppleDto instance // With exhaustive strategy for abstract types @Mapper @BeanMapping(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface FruitMapper { @SubclassMapping(source = Apple.class, target = AppleDto.class) @SubclassMapping(source = Banana.class, target = BananaDto.class) FruitDto toFruitDto(Fruit fruit); // Throws IllegalArgumentException for unknown Fruit subclasses } ``` ## Qualifiers with @Named and @Qualifier Qualifiers disambiguate between multiple mapping methods with the same signature. Use `@Named` for simple string-based selection or create custom qualifier annotations for type-safe selection. ```java // Using @Named for simple qualification public class TitleMapper { @Named("EnglishToGerman") public String translateToGerman(String title) { // translation logic } @Named("GermanToEnglish") public String translateToEnglish(String title) { // translation logic } } @Mapper(uses = TitleMapper.class) public interface MovieMapper { @Mapping(target = "title", qualifiedByName = "EnglishToGerman") GermanMovie toGermanMovie(Movie movie); @Mapping(target = "title", qualifiedByName = "GermanToEnglish") Movie toEnglishMovie(GermanMovie movie); } // Using custom qualifier annotations (type-safe) @Qualifier @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface TitleTranslation { Language from(); Language to(); } public class TitleMapper { @TitleTranslation(from = Language.EN, to = Language.DE) public String translateEnToDe(String title) { } @TitleTranslation(from = Language.DE, to = Language.EN) public String translateDeToEn(String title) { } } @Mapper(uses = TitleMapper.class) public interface MovieMapper { @Mapping(target = "title", qualifiedBy = TitleTranslation.class) GermanMovie toGermanMovie(Movie movie); } ``` ## Null Value Handling Strategies MapStruct provides fine-grained control over how null values are handled during mapping through various strategy annotations. ```java @Mapper( // What to return when entire source is null nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, // What to do when source property is null (for update mappings) nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, // When to generate null checks nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS ) public interface CarMapper { CarDto carToCarDto(Car car); void updateCar(CarDto dto, @MappingTarget Car car); } // NullValueMappingStrategy options: // - RETURN_NULL: return null when source is null (default) // - RETURN_DEFAULT: return empty object/collection when source is null // NullValuePropertyMappingStrategy options (for update methods): // - SET_TO_NULL: set target to null when source is null (default) // - SET_TO_DEFAULT: set target to default value (empty string, 0, empty list) // - IGNORE: don't update target when source is null // Per-mapping configuration @Mapper public interface OrderMapper { @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) void updateOrder(OrderDto dto, @MappingTarget Order order); @Mapping(target = "notes", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) void updateOrderWithDefaults(OrderDto dto, @MappingTarget Order order); } // Collection null handling @Mapper( nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT ) public interface CollectionMapper { List<String> toStringList(List<Integer> source); // Returns empty list if source is null Map<String, String> toStringMap(Map<Long, Date> source); // Returns empty map if source is null } ``` ## Summary MapStruct is ideal for applications requiring high-performance, type-safe object mapping in Java. Common use cases include DTO/entity conversions in REST APIs, transforming domain objects between architectural layers, mapping external API responses to internal models, and converting between different versions of data structures. The compile-time code generation ensures zero runtime overhead, while IDE support provides immediate feedback on mapping errors and auto-completion. Integration with popular frameworks is seamless through component models (Spring, CDI, JSR-330). For complex scenarios, MapStruct supports custom mapping methods, decorators, and lifecycle callbacks. Configuration can be shared across mappers using `@MapperConfig`, and inheritance mechanisms (`@InheritConfiguration`, `@InheritInverseConfiguration`) reduce code duplication. The framework handles collections, maps, enums, builders, and constructors automatically, making it suitable for both simple mappings and complex enterprise applications with polymorphic types and deep object graphs.