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.