In this post under MapStruct, I will show with example how to add custom mapping methods if the out of the box generated mapping methods aren’t sufficient.
For our example I will have the following pojo classes
Person
package package29;
public class Person {
private int id;
private String name;
private Address address;
//Removed getter and setter for brevity
}
Address
package package29;
public class Address {
private String addressLine1;
private String addressLine2;
private String state;
private String country;
//Removed getter and setter for brevity
}
Please note the “Person” class has a reference to “Address” class.
Since we are mapping to DTO classes. Below are the corresponding DTO classes.
PersonDTO
package package29;
public class PersonDTO {
private int id;
private String name;
private AddressDTO addressDTO;
//Removed getter and setter for brevity
}
AddressDTO
package package29;
public class AddressDTO {
private String addressLine;
private String state;
private String country;
//Removed getter and setter for brevity
}
If you compare the “Address” and “AddressDTO” class, “Address” class has two properties “addressLine1” and “addressLine2” to store address, but “AddressDTO” class has only one property “addressLine” to
store the address.
A direct property to property mapping is not possible as the names are different and also the way to store address in “AddressDTO” is also different.
So we need to have custom mapping method to handle this scenario.
Below is the mapper interface with custom mapping method specifically for “Address” to “AddressDTO” class
PersonMapper
package package29;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper
public interface PersonMapper {
@Mapping(target = "addressDTO", source = "address")
PersonDTO personToPersonDTO(Person person);
public default AddressDTO addressToAddressDTO(Address address) {
AddressDTO addressDTO = new AddressDTO();
addressDTO.setAddressLine(address.getAddressLine1() + ", " + address.getAddressLine2());
addressDTO.setState(address.getState());
addressDTO.setCountry(address.getCountry());
return addressDTO;
}
}
In the above interface, I declared a mapping method for “Person” to “PersonDTO” mapping and let the MapStruct handle the mapping.
But as I said previously we cannot let MapStruct handle “Address” to “AddressDTO” mapping as it is not a straight forward mapping.
So we have created our custom mapping method “addressToAddressDTO” as shown above.
MapStruct analyse the interface and generate its own implementation of the interface.
It sees that we have added a custom mapping method for “Address” to “AddressDTO” mapping by checking the custom method parameter and return type.
Based on the analysis, MapStruct will generate the below implementation class
PersonMapperImpl
package package29;
/*
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2025-08-23T12:54:09+0530",
comments = "version: 1.5.5.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.5.jar, environment: Java 21.0.4 (Amazon.com Inc.)"
)
*/
public class PersonMapperImpl implements PersonMapper {
@Override
public PersonDTO personToPersonDTO(Person person) {
if ( person == null ) {
return null;
}
PersonDTO personDTO = new PersonDTO();
personDTO.setAddressDTO( addressToAddressDTO( person.getAddress() ) );
personDTO.setId( person.getId() );
personDTO.setName( person.getName() );
return personDTO;
}
}
As you can see from the above generated code, inside its own implementation of “personToPersonDTO”, when comes to mapping “Address” to “AddressDTO” class, it calls our custom mapping method
“addressToAddressDTO” method. Refer to line 20.
In this way we can add our own custom mapping method that will be automatically called by MapStruct when mapping has to be done.