1. Overview

In this article, we'll go through the new @ServletComponentScan annotation in Spring Boot.

The aim is to support the following Servlet 3.0 annotations:

  • javax.servlet.annotation.WebFilter
  • javax.servlet.annotation.WebListener
  • javax.servlet.annotation.WebServlet

@WebServlet, @WebFilter, and @WebListener annotated classes can be automatically registered with an embedded Servlet container by annotating @ServletComponentScan on a @Configuration class and specifying the packages.

We have introduced the basic usage of @WebServlet in Introduction to Java Servlets and @WebFilter in Introduction to Intercepting Filter Pattern in Java. For @WebListener, you can take a peek at this article which demonstrates a typical use case of web listeners.

2. Servlets, Filters, and Listeners

Before diving into @ServletComponentScan, let's take a look at how the annotations: @WebServlet, @WebFilter and @WebListener were used before @ServletComponentScan came into play.

2.1. @WebServlet

Now we'll first define a Servlet that serves GET requests and responds “hello”:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        try {
            response
              .getOutputStream()
              .write("hello");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

2.2. @WebFilter

Then a filter that filters requests to target “/hello”, and prepends “filtering “ to the output:

@WebFilter("/hello")
public class HelloFilter implements Filter {

    //...
    @Override
    public void doFilter(
      ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
      throws IOException, ServletException {
        servletResponse
          .getOutputStream()
          .print("filtering ");
        filterChain.doFilter(servletRequest, servletResponse);
    }
    //...

}

2.3. @WebListener

Finally, a listener that sets a custom attribute in ServletContext:

@WebListener
public class AttrListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        servletContextEvent
          .getServletContext()
          .setAttribute("servlet-context-attr", "test");
    }
    //...
}

2.4. Deploy to a Servlet Container

Now that we've built the basic components of a simple web application, we can package and deploy it into a Servlet container. Each component's behavior can be readily verified by deploying the packaged war file into Jetty, Tomcat or any Servlet containers that support Servlet 3.0.

3. Using @ServletComponentScan in Spring Boot

You might wonder since we can use those annotations in most Servlet containers without any configuration, why do we need @ServletComponentScan? The problem lies in embedded Servlet containers.

Due to the fact that embedded containers do not support @WebServlet, @WebFilter and @WebListener annotations, Spring Boot, relying greatly on embedded containers, introduced this new annotation @ServletComponentScan to support some dependent jars that use these 3 annotations.

The detailed discussion can be found in this issue on Github.

3.1. Maven Dependencies

To use @ServletComponentScan, we need Spring Boot with version 1.3.0 or above. Let's add the latest version of spring-boot-starter-parent and spring-boot-starter-web to the pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.4.0</version>
    </dependency>
</dependencies>

3.2. Using @ServletComponentScan

The Spring Boot app is pretty simple. We add @ServletComponentScan to enable scanning for @WebFilter, @WebListener and @WebServlet:

@ServletComponentScan
@SpringBootApplication
public class SpringBootAnnotatedApp {

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

}

Without any change to the previous web application, it just works:

@Autowired private TestRestTemplate restTemplate;

@Test
public void givenServletFilter_whenGetHello_thenRequestFiltered() {
 
    ResponseEntity<String> responseEntity = 
      restTemplate.getForEntity("/hello", String.class);
 
    assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
    assertEquals("filtering hello", responseEntity.getBody());
}
@Autowired private ServletContext servletContext;

@Test
public void givenServletContext_whenAccessAttrs_thenFoundAttrsPutInServletListner() {
 
    assertNotNull(servletContext);
    assertNotNull(servletContext.getAttribute("servlet-context-attr"));
    assertEquals("test", servletContext.getAttribute("servlet-context-attr"));
}

3.3. Specify Packages to Scan

By default, @ServletComponentScan will scan from the package of the annotated class. To specify which packages to scan, we can use its attributes:

  • value
  • basePackages
  • basePackageClasses

The default value attribute is an alias for basePackages.

Say our SpringBootAnnotatedApp is under package com.baeldung.annotation, and we want to scan classes in package com.baeldung.annotation.components created in the web application above, the following configurations are equivalent:

@ServletComponentScan
@ServletComponentScan("com.baeldung.annotation.components")
@ServletComponentScan(basePackages = "com.baeldung.annotation.components")
@ServletComponentScan(
  basePackageClasses = 
    {AttrListener.class, HelloFilter.class, HelloServlet.class})

4. Under the Hood

The @ServletComponentScan annotation is processed by ServletComponentRegisteringPostProcessor. After scanning specified packages for @WebFilter, @WebListener and @WebServlet annotations, a list of ServletComponentHandlers will process their annotation attributes, and register scanned beans:

class ServletComponentRegisteringPostProcessor
  implements BeanFactoryPostProcessor, ApplicationContextAware {
  
    private static final List<ServletComponentHandler> HANDLERS;

    static {
        List<ServletComponentHandler> handlers = new ArrayList<>();
        handlers.add(new WebServletHandler());
        handlers.add(new WebFilterHandler());
        handlers.add(new WebListenerHandler());
        HANDLERS = Collections.unmodifiableList(handlers);
    }
    
    //...
    
    private void scanPackage(
      ClassPathScanningCandidateComponentProvider componentProvider, 
      String packageToScan){
        //...
        for (ServletComponentHandler handler : HANDLERS) {
            handler.handle(((ScannedGenericBeanDefinition) candidate),
              (BeanDefinitionRegistry) this.applicationContext);
        }
    }
}

As said in the official Javadoc, @ServletComponentScan annotation only works in embedded Servlet containers, which is what comes with Spring Boot by default.

5. Conclusion

In this article, we introduced @ServletComponentScan and how it can be used to support applications that depend on any of the annotations: @WebServlet, @WebFilter, @WebListener.

The implementation of the examples and code can be found in the GitHub project.