javaspringspring-bootspring-restmapstruct

Unable to autowire the MapStruct mapper


I was following this tutorial about the partial update. As instructed, I created mapper interface with proper annotations.

Here's the mapper

@Mapper(componentModel = "spring")
public interface UserEntityMapper {
  
  @Mapping(source = "password", target = "password")
  @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
  void updatePasswordFromDTO(PasswordResetRequest dto, @MappingTarget User entity);
}

As per the tutorial, @Mapper(componentModel = "spring") generates mapper as a Spring bean that can be retrieved via @Autowired.

But when I tried to do same in my service layer class,

@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
  ...
  @Autowired
  private UserEntityMapper userMapper;
  ...
}

I get this error, my application failed to start.

***************************
APPLICATION FAILED TO START
***************************

Description:

Field userMapper in com.application.services.AccountServiceImpl required a bean of type 'com.application.mappers.UserEntityMapper' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.application.mappers.UserEntityMapper' in your configuration.

And at last, here's pom.xml of my project.

<?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>2.5.5</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.myproject</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>authentication-template</name>
    <description>Description</description>
    <properties>
        <java.version>11</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-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</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-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</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>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-freemarker -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.4.2.Final</version>
        </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>

</project>

I also tried these different solution, but same result.

  1. Answer By @Som
  2. Answer by @Gunnar

Edit 1

Here's @SpringBootApplication file.

@SpringBootApplication
@PropertySources(value = {
  @PropertySource("classpath:mail.properties"),
  @PropertySource("classpath:messages.properties"),
  @PropertySource("classpath:security.properties")
})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Edit 2

Package structure of the project

Package structure of the project

Please help, Thank you :)


Solution

  • I know, I'm too late. Apologies for the delay in responding to the question. Let's dive into configuring MapStruct.

    The actual issue was with configuration. I follow the following steps to successfully configure MapStruct with Spring Boot.

    1. First add mapstruct dependency in pom.xml.

      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
          <scope>provided</scope> <!-- Required -->
      </dependency>
      <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct</artifactId>
          <version>1.5.5.Final</version>
      </dependency>
      

      Note: Make sure <scope>provided</scope> is set for lombok. Without that @Builder was not working for me.

    2. Since mapstruct requires annotation processing, so we need to add the plugin into our pom.xml.

      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.11.0</version>
          <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-mapstruct-binding</artifactId>
                      <version>0.2.0</version>
                  </path>
                  <path>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                      <version>1.18.30</version>
                  </path>
              </annotationProcessorPaths>
          </configuration>
      </plugin>
      

      Note: lombok-mapstruct-binding is required for Lombok and Mapstruct, to allow them to cooperate.

    3. Make sure annotation processing is enable in you IDE. You can check these links for IntelliJ Idea and Eclipse/STS

    4. Once done, you can create mapper interface.

      import static org.mapstruct.MappingConstants.ComponentModel.SPRING;
      
      @Mapper(componentModel = SPRING)
      public interface UserMapper {
        UserModel dtoToModel(UserDTO userDto);
      }
      
    5. Then the mapper can be easily @Autowired.

      @Service
      public class UserService {
      
          private final UserMapper userMapper;
      
          @Autowired
          public UserService(UserMapper userMapper) {
              this.userMapper= userMapper;
          }
      
          // Business logic
      }
      

    I hope this will help. Thanks and Regards.