DEV Community

hairshkumar
hairshkumar

Posted on

Map Struct Tutorial - autogenerate mapper code in java

Normal mapping

public class Mapper {

    public Employee toEmployee(EmployeeDTO dto) {

        Employee employee=new Employee();

        employee.setName(dto.getName());
        employee.setJobTitle(dto.getJobTitle());
        employee.setJoinedDate(dto.getJoinedDate());
        employee.setMobileNo(dto.getMobileNo());
        employee.setDepartment(dto.getDepartment());
        employee.setSalary(dto.getSalary());

        return employee;

    }
}
Enter fullscreen mode Exit fullscreen mode

what is mapstruck?

MapStruct is a code generator that greatly simplifies the implementation of mappings between Java models and DTO. Using Mapstruck we don’t have to long mapper code instead simply we need to say source and destination class.

MapStruck automatically generates mapper code which maps fields based on name from source to destination class also we can pass custom mapping for field.

mapstruck maven dependency

<org.mapstruct.version>1.5.2.Final</org.mapstruct.version>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

if you are using Lombok to reduce boilerplate cod in entity or DTO class then add below in pom.xml

How to create a Mapper in mapstruck?

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${org.projectlombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
Enter fullscreen mode Exit fullscreen mode

Create an interface and annotate with @Mapper. Now let's define a method. The method parameter acts as source and method return type acts as Target. MapStruck will map source fields to target fields if they have the same field name.

Mapping fields with same name

By default, MapStruck maps fields by name if source and target have fields with same name then mapper maps the field. The EmployeeDTO and Employee both classes have the same property names.

@Mapper(componentModel = "spring")
public interface EmployeeMapper {

    Employee toEmployee(EmployeeDTO employeeDTO);

}
Enter fullscreen mode Exit fullscreen mode

The componentModel = “spring” define this mapper as spring bean which we can autowire in our application.

The generated class code will look like below the source and target classes both having the same property names.

@Component
public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public Employee toEmployee(EmployeeDTO employeeDTO) {
        if ( employeeDTO == null ) {
            return null;
        }

        Employee employee = new Employee();

        employee.setDepartment( employeeDTO.getDepartment() );
        employee.setGender( employeeDTO.getGender() );
        employee.setId( employeeDTO.getId() );
        employee.setJobTitle( employeeDTO.getJobTitle() );
        employee.setJoinedDate( employeeDTO.getJoinedDate() );
        employee.setMobileNo( employeeDTO.getMobileNo() );
        employee.setName( employeeDTO.getName() );
        employee.setSalary( employeeDTO.getSalary() );

        return employee;
    }
}
Enter fullscreen mode Exit fullscreen mode

Mapping fields with different name

In most of the cases, the source and target classes will not have same field names so now how to map these fields for this we have @Mapping annotation which take the target field name and soruce field name map these field. We can give custom mapping logic for fields in @Mapping annotation.

@Mappings takes group of @Mapping annotation if there is more mapping logic than its better give them inside @Mappings.

public interface EmployeeMapper {

    @Mappings({
        @Mapping(target = "jobTitle", source = "title"),
        @Mapping(target = "joinedDate", source = "startingdate")
    })
    Employee toEmployee(EmployeeDTO employeeDTO);

}
Enter fullscreen mode Exit fullscreen mode

Mapping nested beans fields

Here we will see how we can map nested object fields if you see example below the Employee class has Personal object which has name, gender, age and Address object.

We have to give full path to the field example personal.address.streetaddress in target, so mapper will search for streetaddress inside address in personal object.

@Mappings({ 
    @Mapping(target = "personal.name", source = "firstName"),
    @Mapping(target = "personal.gender", source = "gender"), 
    @Mapping(target = "personal.age", source = "age"),
    @Mapping(target = "personal.address.streetaddress", source = "dto.primaryAddr.line1"),
    @Mapping(target = "personal.address.city", source = "dto.primaryAddr.city"),
    @Mapping(target = "personal.address.postalcode", source = "dto.primaryAddr.postalCode") 
})
Employee toEmployee(EmployeeDTO dto);
Enter fullscreen mode Exit fullscreen mode

Mapping custom method

In some cases it can be required to manually implement a specific mapping from one type to another which can’t be generated by MapStruct. One way to handle this is to implement the custom method on another class which then is used by mappers generated by MapStruct

Define java 8 default method in the interface and provide the implementation. We use @Named to name the method.

In the @Mapping define the source and target, give the @Named method name in the qualifiedByName attributes so the mapstruck will generate the code and call the default method

package com.example.graphql.mapstruck;

import java.util.ArrayList;
import java.util.List;

@Mapper(componentModel = "spring")
public interface EmployeeMapper {

    @Mapping(target="primaryAddr",source ="personal.address",qualifiedByName = "addressdto" )
    EmployeeDTO toEmployeeDto(Employee employee);

    @Named("addressdto")
    default PrimaryAddr composeAddress(Address address) {

        PrimaryAddr primaryAddr=new PrimaryAddr();

        primaryAddr.setLine1(address.getStreetaddress());
        primaryAddr.setCity(address.getCity());
        primaryAddr.setPostalCode(address.getPostalcode());

        return primaryAddr;

    }

}
Enter fullscreen mode Exit fullscreen mode

Let see how mapstruck have generated the mapper code for the above custom method.The generated mapper code internally call our custom method for mapping address.

public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public EmployeeDTO toEmployeeDto(Employee employee) {
        if ( employee == null ) {
            return null;
        }

        EmployeeDTO employeeDTO = new EmployeeDTO();

        employeeDTO.setPrimaryAddr( composeAddress( employeePersonalAddress( employee ) ) );

        return employeeDTO;
    }

    private Address employeePersonalAddress(Employee employee) {
        if ( employee == null ) {
            return null;
        }
        Personal personal = employee.getPersonal();
        if ( personal == null ) {
            return null;
        }
        Address address = personal.getAddress();
        if ( address == null ) {
            return null;
        }
        return address;
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)