In previous post under MapStruct, I showed how to create a custom mapper method and how MapStruct framework calls it.
For recap,
1) sometimes we cannot depend on the mapping method created by MapStruct. So we create our own custom mapper method which takes care of mapping one object properties to another object properties.
2) We instruct MapStruct to call this custom mapping method when it has to map the object for which this custom mapper method is created.
3) MapStruct figures out whether there is a custom mapping method and for which object, based on the method argument and return type.
We can also instruct MapStruct to pass the target Class type as argument to the custom mapper method.
To do this we use MapStruct provided “@TargetType” annotation to the custom mapper method argument which has to receive class target type.
Lets see an example. For our example, we have to map “Person” object to “PersonDTOWithAddressDTO1” and again the same “Person” object to “PersonDTOWithAddressDTO2” object.
Below is the structure of “Person” class.
Person
package package32;
public class Person {
private int id;
private String name;
private Address address;
//removed getter, setter, and toString for brevity
}
Below is structure of “Address” class
Address
package package32;
public class Address {
private String addressLine1;
private String addressLine2;
private String state;
private String country;
//removed getter, setter, and toString for brevity
}
The main difference between “PersonDTOWithAddressDTO1” and “PersonDTOWithAddressDTO2” is that “PersonDTOWithAddressDTO1” has a reference to “AddressDTO1” object and “PersonDTOWithAddressDTO2” has a
reference to “AddressDTO2”
Below are the class structure of “AddressDTO1” and “AddressDTO2”
AddressDTO1
package package32;
public class AddressDTO1 implements IAddressDTO {
private String addressLine;
private String state;
private String country;
//removed getter, setter, and toString for brevity
}
AddressDTO2
package package32;
public class AddressDTO2 implements IAddressDTO {
private String addressLine;
private String state;
private String country;
//removed getter, setter, and toString for brevity
}
Both “AddressDTO1” and “AddressDTO2” class implements “IAddressDTO” interface whose structure is as shown below
IAddressDTO
package package32;
public interface IAddressDTO {
}
Now below is the mapper interface
PersonMapper
package package32;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.TargetType;
@Mapper
public interface PersonMapper {
@Mapping(target = "addressDTO", source = "address")
PersonDTOWithAddressDTO1 personToPersonDTOWithAddressDTO1(Person person);
@Mapping(target = "addressDTO", source = "address")
PersonDTOWithAddressDTO2 personToPersonDTOWithAddressDTO2(Person person);
public default <T extends IAddressDTO> T addressToAddressDTO(Address address, @TargetType Class<T> classType) {
IAddressDTO addressDTO = null;
if(classType.equals(AddressDTO1.class)) {
AddressDTO1 addressDTO1 = new AddressDTO1();
addressDTO1.setAddressLine(address.getAddressLine1() + ", " + address.getAddressLine2());
addressDTO1.setState(address.getState());
addressDTO1.setCountry(address.getCountry());
addressDTO = addressDTO1;
} else {
AddressDTO2 addressDTO2 = new AddressDTO2();
addressDTO2.setAddressLine(address.getAddressLine1() + ", " + address.getAddressLine2());
addressDTO2.setState(address.getState());
addressDTO2.setCountry(address.getCountry());
addressDTO = addressDTO2;
}
return classType.cast(addressDTO);
}
}
In the above interface we have “personToPersonDTOWithAddressDTO1” and “personToPersonDTOWithAddressDTO2” methods whose implementations will be generated by MapStruct.
The generated implementations of “personToPersonDTOWithAddressDTO1” and “personToPersonDTOWithAddressDTO2” methods will call “addressToAddressDTO” custom mapper method passing the target class type and the source object as argument.
The “addressToAddressDTO” custom mapper method takes two arguments
1) the source object which in this case is “Address” object
2) the class type of the target object, which in this case can be “AddressDTO1.class” or “AddressDTO2.class”.
And returns an instance which implements “IAddressDTO” interface.
Below is the MapStruct generated implementations of “personToPersonDTOWithAddressDTO1” and “personToPersonDTOWithAddressDTO2” methods
PersonMapperImpl
package package32;
/*
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2025-12-20T13:10:23+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 PersonDTOWithAddressDTO1 personToPersonDTOWithAddressDTO1(Person person) {
if ( person == null ) {
return null;
}
PersonDTOWithAddressDTO1 personDTOWithAddressDTO1 = new PersonDTOWithAddressDTO1();
personDTOWithAddressDTO1.setAddressDTO( addressToAddressDTO( person.getAddress(), AddressDTO1.class ) );
personDTOWithAddressDTO1.setId( person.getId() );
personDTOWithAddressDTO1.setName( person.getName() );
return personDTOWithAddressDTO1;
}
@Override
public PersonDTOWithAddressDTO2 personToPersonDTOWithAddressDTO2(Person person) {
if ( person == null ) {
return null;
}
PersonDTOWithAddressDTO2 personDTOWithAddressDTO2 = new PersonDTOWithAddressDTO2();
personDTOWithAddressDTO2.setAddressDTO( addressToAddressDTO( person.getAddress(), AddressDTO2.class ) );
personDTOWithAddressDTO2.setId( person.getId() );
personDTOWithAddressDTO2.setName( person.getName() );
return personDTOWithAddressDTO2;
}
}
As you can see from the above code, at line 20, “personToPersonDTOWithAddressDTO1” method calls “addressToAddressDTO” method passing the source object and class object of “AddressDTO1” as argument.
At line 35, “personToPersonDTOWithAddressDTO2” method calls “addressToAddressDTO” method passing the source object and class object of “AddressDTO2” as argument.
In this way we can instruct MapStruct to pass class type of target object as method argument to custom mapper methods.
Below is the output
Output
PersonDTO{id=1, name='John Doe', addressDTO1=AddressDTO1{addressLine='addressLine1, addressLine2', state='Virginia', country='USA'}}
PersonDTO{id=1, name='John Doe', addressDTO2=AddressDTO2{addressLine='addressLine1, addressLine2', state='Virginia', country='USA'}}