Search code examples
spring-bootmappingdtomapstruct

MapStruct Generating Incorrect Mapping Implementation


I'm encountering an issue with MapStruct where the generated implementation is not correctly mapping the fields between my User and UserDto entities.

Here is the interface I'm defining:

@Mapper(componentModel = "spring")
public interface UserMapper {
UserDto userToUserDto(User user);
User userDtoToUser(UserDto userDto);
List\<UserDto\> usersToUserDtos(List\<User\> users);
}

However, the generated implementation is incorrect and does not map the fields as expected:

@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-08-03T18:03:32+0100",
comments = "version: 1.6.0.Beta1, compiler: javac, environment: Java 17.0.11 (Amazon.com Inc.)"
)
@Component
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDto userToUserDto(User user) {
        if ( user == null ) {
            return null;
        }
    
        UserDto userDto = new UserDto();
    
        return userDto;
    }
    
    @Override
    public User userDtoToUser(UserDto userDto) {
        if ( userDto == null ) {
            return null;
        }
    
        User user = new User();
    
        return user;
    }
    
    @Override
    public List<UserDto> usersToUserDtos(List<User> users) {
        if ( users == null ) {
            return null;
        }
    
        List<UserDto> list = new ArrayList<UserDto>( users.size() );
        for ( User user : users ) {
            list.add( userToUserDto( user ) );
        }
    
        return list;
    }

}

Here are my User and UserDto entities:

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

    @NotBlank(message = "Name is mandatory")
    private String name;
    
    @NotBlank(message = "Password is mandatory")
    @Size(min = 8, message = "Password should have at least 8 characters")
    private String password;
    
    @JsonFormat(pattern = "dd/MM/yyyy")
    @Past(message = "Birth date should be in the past")
    private LocalDate birthDate;
    
    @NotNull(message = "VIP status is mandatory")
    private Boolean isVIP = false;
    
    private String vipCode;

}

@Data
public class UserDto {

    private Long id;
    
    @JsonProperty("full name")
    private String name;
    
    @JsonIgnore
    private String password;
    
    @JsonFormat(pattern = "dd/MM/yyyy")
    private String birthDate;
    
    @NotNull(message = "VIP status is mandatory")
    private Boolean isVIP = false;
    
    private String vipCode;

}

Versions:

Java: 17
Spring Boot: 3.3.2

org.mapstruct
mapstruct
1.6.0.Beta1

org.mapstruct
mapstruct-processor
1.6.0.Beta1

Here is my pom.xml file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.in28</groupId>
<artifactId>socialMedia28</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>socialMedia28</name>
<description>socialMedia28</description>
<url/>
<licenses>
    <license/>
</licenses>
<developers>
    <developer/>
</developers>
<scm>
    <connection/>
    <developerConnection/>
    <tag/>
    <url/>
</scm>
<properties>
    <java.version>17</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-hateoas</artifactId>
    </dependency>

    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.6.0.Beta1</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.6.0.Beta1</version>
    </dependency>

    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.6.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

Does anyone know what might be causing MapStruct to generate these incorrect implementations? Any help would be greatly appreciated!

I have defined the UserMapper interface with the expectation that MapStruct would automatically generate a correct implementation to map fields between User and UserDto. I expected the generated implementation to correctly map all fields, including name, password, birthDate, isVIP, and vipCode. However, the generated implementation does not map any fields, resulting in empty UserDto and User objects.


Solution

  • Based on your pom.xml MapStruct might not work correctly with Lombok (You are using @Data).

    The reason behind this is that Lombok generates getter and setter methods at compile time, but MapStruct needs to see these methods to generate the mapping code.

    So to fix this issue you need to update your pom.xml as follows :

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
    
                ...
                ...
    
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.5.Final</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>1.18.34</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
    

    By specifying the annotation processor order, MapStruct is now able to see the methods that Lombok generates.

    PS: make sure to update the versions to match the versions you are using in your project.