MapStruct's @InheritConfiguration and @InheritInverseConfiguration annotations streamline complex mapping hierarchies and bidirectional conversions. These tools eliminate repetitive @Mapping definitions while maintaining type safety and compile-time validation.
Core Concepts
@InheritConfiguration copies forward mapping rules from a base method to a child method with identical source/target types. Local mappings override inherited ones for flexibility.
@InheritInverseConfiguration automatically reverses a forward mapping by swapping source and target types. It handles field renames and ignores, generating clean DTO-to-entity roundtrips.
Both require MappingInheritanceStrategy.EXPLICIT (MapStruct default).
Complete Working Example
Domain Models
public class Car {
private String make;
private int numberOfSeats;
private int doors;
public Car() {}
public Car(String make, int numberOfSeats, int doors) {
this.make = make;
this.numberOfSeats = numberOfSeats;
this.doors = doors;
}
// Getters and setters
public String getMake() { return make; }
public void setMake(String make) { this.make = make; }
public int getNumberOfSeats() { return numberOfSeats; }
public void setNumberOfSeats(int numberOfSeats) { this.numberOfSeats = numberOfSeats; }
public int getDoors() { return doors; }
public void setDoors(int doors) { this.doors = doors; }
}
public class CarDto {
private String manufacturer;
private Integer seatCount;
private boolean hasNavigationSystem;
public CarDto() {}
public CarDto(String manufacturer, Integer seatCount, boolean hasNavigationSystem) {
this.manufacturer = manufacturer;
this.seatCount = seatCount;
this.hasNavigationSystem = hasNavigationSystem;
}
// Getters and setters
public String getManufacturer() { return manufacturer; }
public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; }
public Integer getSeatCount() { return seatCount; }
public void setSeatCount(Integer seatCount) { this.seatCount = seatCount; }
public boolean isHasNavigationSystem() { return hasNavigationSystem; }
public void setHasNavigationSystem(boolean hasNavigationSystem) { this.hasNavigationSystem = hasNavigationSystem; }
}
Inheritance Hierarchy Example
import org.mapstruct.InheritConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper
public interface VehicleMapper {
@Mapping(target = "hasNavigationSystem", ignore = true)
@Mapping(target = "manufacturer", source = "make")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDto vehicleToDto(Car vehicle);
}
@Mapper
public interface CarMapper extends VehicleMapper {
@InheritConfiguration(name = "vehicleToDto")
CarDto carToCarDto(Car car);
// Custom mapping overrides inheritance
@InheritConfiguration(name = "vehicleToDto")
@Mapping(target = "hasNavigationSystem", constant = "false")
CarDto carToBasicDto(Car car);
}
Bidirectional Mapping Example
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper
public interface BidirectionalCarMapper {
// Forward mapping with field renaming
@Mapping(target = "hasNavigationSystem", ignore = true)
@Mapping(target = "seatCount", source = "numberOfSeats")
@Mapping(target = "manufacturer", source = "make")
CarDto toDto(Car car);
// Automatically reverses: seatCount->numberOfSeats, manufacturer->make
@InheritInverseConfiguration(name = "toDto")
@Mapping(target = "doors", ignore = true)
Car toEntity(CarDto dto);
// Selective reverse with local overrides
@InheritInverseConfiguration
@Mapping(target = "numberOfSeats", ignore = true)
@Mapping(target = "doors", ignore = true)
Car toEntityNoSeats(CarDto dto);
}
Verification Demo
import org.mapstruct.factory.Mappers;
public class MapStructInheritanceDemo {
public static void main(String[] args) {
CarMapper carMapper = Mappers.getMapper(CarMapper.class);
BidirectionalCarMapper biMapper = Mappers.getMapper(BidirectionalCarMapper.class);
// Test inheritance
Car ferrari = new Car("Ferrari", 2, 2);
CarDto dto = carMapper.carToCarDto(ferrari);
System.out.println(dto.getManufacturer()); // "Ferrari"
System.out.println(dto.getSeatCount()); // 2
// Test bidirectional
CarDto sportsCarDto = new CarDto("Porsche", 4, true);
Car entity = biMapper.toEntity(sportsCarDto);
System.out.println(entity.getMake()); // "Porsche"
System.out.println(entity.getNumberOfSeats()); // 4
}
}
Comparison Table
| Feature | @InheritConfiguration |
@InheritInverseConfiguration |
|---|---|---|
| Direction | Forward → Forward | Forward → Reverse |
| Type Match | Identical source/target | Swapped source/target |
| Primary Use | Class hierarchies | DTO ↔ Entity roundtrips |
Best Practices
For @InheritConfiguration:
- Use in mapper hierarchies (
extends) - Document base mappings clearly
- Override selectively with
@Mapping
For @InheritInverseConfiguration:
- Specify
name=when source mapper has multiple similar methods - Use
@Mapping(ignore=true)for one-way fields
These annotations reduce mapping code by 70-80% in typical enterprise applications while maintaining full compile-time safety.
Leave a Reply