Spring @ComponentScan not detecting REST controller

Viewed 6137

After searching various similar question on StackOverflow, I have determined that most people suffering from this issue are not properly scanning the module where their controller lives. Some solutions call for combining the files that are to be scanned into the same module as the Application (which works), but I do not want to move any of my .java files. Instead, I'd like to make @ComponentScan work.

I have the following project structure

Project
      |
      settings.gradle
      build.gradle
      module1
            |
            src/main/java/com.test.application
            |                                |
            |                                Application.java
            |                                SwaggerConfig.java
            build.gradle
      module2
            |
            src/main/java/com.test.service
            |                            |
            |                            Service.java
            |                            Controller.java
            build.gradle

Application.java

@SpringBootApplication
@ComponentScan("com.test")
public class Application {

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

Note here that I am scanning com.test in my component scan. I can't find anything in the @ComponentScan documentation (or on StackOverflow) that would suggest I am using @ComponentScan incorrectly.

Controller.java

@RestController
@RequestMapping("/test")
public class GearParsingController {
  private SomeService someService;

  @Autowired
  public SomeController(SomeService someService) {
    this.someService = someService;
  }

  @GetMapping("/path")
  public ResponseEntity<String> getSomeService() {
    return new ResponseEntity<String>("Default message", HttpStatus.OK);
  }
}

Project build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.6.RELEASE")
    }
}

subprojects {
    repositories {
        mavenCentral()
    }

    apply plugin: 'java'
    apply plugin: 'idea'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'
}

module1 build.gradle

dependencies {
    implementation project(':module2')

    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.1.6.RELEASE'
}

module2 build.gradle

dependencies {
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.1.6.RELEASE'

    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0-RC2'
}

When I try to access my endpoints at localhost:8080/test/path I am met with:

{
    "timestamp": "2019-06-29T19:19:52.275+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/test/path"
}

When launching the program in debug mode, I am given the following output:

2019-06-29 13:58:58.345  INFO 8272 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-06-29 13:58:58.387  INFO 8272 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-06-29 13:58:58.387  INFO 8272 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.21]
2019-06-29 13:58:58.522  INFO 8272 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-06-29 13:58:58.522 DEBUG 8272 --- [           main] o.s.web.context.ContextLoader            : Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
2019-06-29 13:58:58.522  INFO 8272 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1786 ms
2019-06-29 13:58:58.544 DEBUG 8272 --- [           main] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: characterEncodingFilter urls=[/*], hiddenHttpMethodFilter urls=[/*], formContentFilter urls=[/*], requestContextFilter urls=[/*]
2019-06-29 13:58:58.545 DEBUG 8272 --- [           main] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
2019-06-29 13:58:58.583 DEBUG 8272 --- [           main] o.s.b.w.s.f.OrderedRequestContextFilter  : Filter 'requestContextFilter' configured for use
2019-06-29 13:58:58.583 DEBUG 8272 --- [           main] .s.b.w.s.f.OrderedHiddenHttpMethodFilter : Filter 'hiddenHttpMethodFilter' configured for use
2019-06-29 13:58:58.583 DEBUG 8272 --- [           main] s.b.w.s.f.OrderedCharacterEncodingFilter : Filter 'characterEncodingFilter' configured for use
2019-06-29 13:58:58.584 DEBUG 8272 --- [           main] o.s.b.w.s.f.OrderedFormContentFilter     : Filter 'formContentFilter' configured for use
2019-06-29 13:58:58.896 DEBUG 8272 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 5 mappings in 'requestMappingHandlerMapping'
2019-06-29 13:58:58.990  INFO 8272 --- [           main] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [public org.springframework.http.ResponseEntity<springfox.documentation.spring.web.json.Json> springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation(java.lang.String,javax.servlet.http.HttpServletRequest)]
2019-06-29 13:58:59.045 DEBUG 8272 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Patterns [/**/favicon.ico] in 'faviconHandlerMapping'
2019-06-29 13:58:59.128  INFO 8272 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-06-29 13:58:59.146 DEBUG 8272 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2019-06-29 13:58:59.257 DEBUG 8272 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2019-06-29 13:58:59.263 DEBUG 8272 --- [           main] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2019-06-29 13:58:59.442  INFO 8272 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2019-06-29 13:58:59.481  INFO 8272 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2019-06-29 13:58:59.519  INFO 8272 --- [           main] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references

So, my question is: Why isn't ComponentScan detecting my REST controller?

4 Answers

Make your files this way:

Project build.gradle (removed spring plugin application to all module projects)

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.6.RELEASE")
    }
}

subprojects {
    repositories {
        mavenCentral()
    }

    apply plugin: 'java'
    apply plugin: 'idea'
}

module1 build.gradle (apply spring plugins and bootJar goal)

dependencies {
    implementation project(':module2')

    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.1.6.RELEASE'
}

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
    baseName = 'module1'
    version = '0.0.1-SNAPSHOT'
}

module2 build.gradle (added jar goal)

dependencies {
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.1.6.RELEASE'
}

jar {
    baseName = 'module2'
    version = '0.0.1-SNAPSHOT'
}

settings.gradle

include 'module1'
include 'module2'

Point is that we want second module built as normal jar and the main module as spring boot runnable. With that setup just gradle build and then gradle bootRun from main project folder.

I think if you want to pick up your controller from the "com.test" package use the basePackages option of the @ComponentScan:

@ComponentScan(basePackages = "com.test")

Your main application Application.java is under the directory com.test.application , which means that when the springboot application starts up by default the @SpringBootApplication annotation will look for beans and configurations under the packgae com.test.application . In the springboot documentation it is specified that we should place the Main application under the root package so that spring scans everything under it.

@SpringBootApplication is a convenience annotation that adds all of the following:

  • @Configuration tags the class as a source of bean definitions for the application context.
  • @EnableAutoConfiguration tells Spring Boot to start adding beans based on classpath settings, other beans, and various property
    settings.
  • Normally you would add @EnableWebMvc for a Spring MVC app, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath. This flags the application as a web application and
    activates key behaviors such as setting up a DispatcherServlet.
  • @ComponentScan tells Spring to look for other components, configurations, and services in the hello package, allowing it to
    find the controllers.

So, to fix your issue try moving your main application to the root of your package com.test or you could use the annotation @ComponentScan to specify which all packages are needed to be scanned like :

@ComponentScan(basePackages = "com.test")

According to Spring Boot convention, controllers have to be in the com.test.controller package.

Related