Search code examples
spring-bootserviceautowired

spring boot fails to inject service with "could not be found" error. @Autowired not working


@Autowired is not working, and we don't know why. The service and controller are in different modules and packages, but we assume this does not matter. We can manually instantiate the service, so the controller module is able to "see" the common module.

Running the application results in the following error:

Parameter 0 of constructor in com.xx.campaign_api.controller.MyController required a bean of type 'com.xx.campaign.common.service.MyService' that could not be found.

We have multi-module spring boot 3.4.2 project with following pom:

<project xxx
    <groupId>org.springframework</groupId>
    <artifactId>our-service</artifactId>
    <version>0.1.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>campaign-common</module>
        <module>campaign-api</module>
        <module>campaign-schedule</module>
    </modules>
</project>

in the api module we have a rest controller like this:

package com.xx.campaign_api.controller;
import com.xx.campaign.common.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    private MyService testService;

    @Autowired
    public MyController(MyService testService) {  // THIS LINE IS FAILING
        this.testService = testService;
    }

    @GetMapping("/test")
    public String test() {
    return testService.test();
    }
}

The service looks like this:

package com.xxx.campaign.common.service;
import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService {
    @Override
    public String test() {
        return "test3";
    }
}
package com.xxx.campaign.common.service;
public interface MyService {
    public String test();
}

The main class looks like this:

package com.xx.campaign_api;
@SpringBootApplication
public class CampaignApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(CampaignApiApplication.class, args);
    }
}

in the pom of the controller module, we have:

    <dependency>
        <groupId>xx</groupId>
        <artifactId>campaign-common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>

I also tried this:

package com.xx.campaign_api.controller
@SpringBootApplication(scanBasePackages = "com.xx")
@RestController
public class GaController {

    private MyService myService;

    @Autowired
    public GaController(MyService myService) {  // THIS LINE IS FAILING
        this.myService = myService;
    }

But this app wont start with this error:

Web application could not be started as there was no org.springframework.boot.web.servlet.server.ServletWebServerFactory bean defined in the context.

Solution

  • The problem is your generic service and your API have different packages and don't really share a common super package (well they do in the form of com.xx).

    Next don't put multiple @SpringBootApplication annotations in your code that only will result in errors. So remove the @SpringBootApplication from the @RestController class.

    You can do 3 things to fix this.

    1. Move your @SpringBootApplication annotated class (the one with the main into com.xx and start it from there.
    2. Add a scanBasePackages to your @SpringBootApplication, while this work for detecting components if you have things like entities etc. in there it won't consider those.
    3. Restructure your application a little and move your @SpringBootApplication to a top-level shared package.

    Move the @SpringBootApplication annotated class

    package com.xx;
    
    @SpringBootApplication
    public class CampaignApiApplication {
        public static void main(String[] args) {
            SpringApplication.run(CampaignApiApplication.class, args);
        }
    }
    

    While this will probably work, you might run the risk of scanning too much, depending of the name of the xx.

    Add scanBasePackages to the @SpringBootApplication annotation

    package com.xx.campaign_api;
    
    @SpringBootApplication(scanBasePackages={
     "com.xx.campaign", "com.xx.campaign_api"
    })
    public class CampaignApiApplication {
        public static void main(String[] args) {
            SpringApplication.run(CampaignApiApplication.class, args);
        }
    }
    

    While this will work for detecting components like services, repositories etc. If you have things like entities or Spring Data repositories in those packages that won't work. To make that work you would have to add additional annotations like @EntityScan and @EnableJpaRepositories (or whatever persistence technology you use). Adding all those annotations is cumbersome and can lead to surprises as parts of auto configuration not being auto config anymore.

    Restructure packages and move @SpringBootApplication class.

    package com.xx.campaign;
    
    @SpringBootApplication
    public class CampaignApiApplication {
        public static void main(String[] args) {
            SpringApplication.run(CampaignApiApplication.class, args);
        }
    }
    

    Now I would suggest to do this and rename your com.xx.campaign_api package to com.xx.campaign.api. This would make it a sub package of com.xx.campaign. Now when placing the CampaignApiApplication in the com.xx.campaign package it will automatically detect everything in com.xx.campaign and all of its sub-packages. Without adding any additional annotations.

    This last one is also the recommended one from the Spring Boot developers and mentioned in the Spring Boot reference guide.