My @Configuration class did not get applied because of this silly mistake

·

5 min read

When working with Spring Boot, it is important to understand how Beans are created and injected into your application’s runtime context so that other components of your app can consume those Beans. Now, Spring Boot usually handles this behind the scenes and this process is abstracted to you by the means of the @Autowired annotation. But Spring Boot has also provided control to developers regarding the Beans. One such annotation is the @ComponentScan.

Just to give you a little bit of context, this is an account of a small misunderstanding that cost me and my mentors 2 days of debugging. Keeping our compliance policies in mind, I won’t be showing you the actual code but I have simulated it in another demo project. This is how the package structure looks like: image.png

The main class looked like below:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo.*"})
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

It’s a very simple Spring Boot application that has a model – HelloWorld. This is used to represent some real-world entity and is a simple Java Bean with a single property – message. This is what the class looks like:

package com.example.demo.models;
public class HelloWorld {
    private String message;
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
}

We have a configuration class that is supposed to set up this Entity i.e. create a Bean for this HelloWorld class and inject it into the runtime context of the application. This is what the DemoConfig class looks like:

package com.example.demo;

import com.example.demo.com.example.demo.models.HelloWorld;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DemoConfig {
    @Bean
    public HelloWorld helloWorld() {
        HelloWorld world = new HelloWorld();
        world.setMessage("Hello World from Config!");
        return world;
    }
}

As I said, Spring Boot handles a lot of configuration for us. So marking the helloWorld() method with the @Bean annotation basically means that the world object of type HelloWorld that we initialized inside this method is created and injected into the application’s runtime context – basically meaning that is made available for other parts of your application to use. So which part of my application is using this bean? That would be the DemoController – a REST controller that is utilizing this resource when sending a response to the client:

package com.example.demo.controllers;

import com.example.demo.com.example.demo.models.HelloWorld;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
    @Autowired
    private HelloWorld world;

    @GetMapping("/")
    public String testController() {
        return world.getMessage();
    }
}

When we mark a private member with @Autowired, it basically means that at runtime, when Spring Boot is instantiating this class and making a Bean out of it (since this class is marked with @RestController, a Bean for this class is also made), the dependency on a Bean of type HelloWorld must be resolved. So how is this resolved? From the application’s runtime context. So Spring Boot says, “Okay! I need to create a Bean for DemoController. To succeed in that, I need a Bean of type HelloWorld. Hey Application’s Runtime context. Do you have a Bean of type HelloWorld that I can inject into my DemoController?” The Application Runtime Context says, “Let me check! Voila! I do.” So Spring Boot says, “Great! I have created a Bean of DemoController. Onto the next bean!”

The Issue


When I run this application, since I have written my @Configuration class correctly, it should work and when I hit the base URL for the application, I should see the “Hello World from Config!” message. Unfortunately, the application failed to start and I see this error message: image.png

So Spring Boot is telling me “I could not create the DemoController bean for you because I could not find the Bean it is dependent on i.e. HelloWorld.” Now, I could create a new object of type HelloWorld inside my RestController API, set its message property, and return that like so:

@RestController
public class DemoController {
//    @Autowired
//    private HelloWorld world;
    @GetMapping("/")
    public String testController() {
        HelloWorld world = new HelloWorld();
        world.setMessage("Hello World from Controller!");
        return world.getMessage();
    }
}

But as you all know, this object will be limited to the scope of this API. Instead, I want a singleton instance of this HelloWorld to be injected as a Bean in my application and use it throughout. This was a fairly simple example but in actuality, the @Configuration class was setting up a Bean with values fetched from a secured store. So the first day, I thought that this was a problem with the store and my application couldn’t connect to it. So I texted a bunch of IT admins asking around stuff like “Do I have the permission?”, “My auth tokens aren’t working.” and some other things. But of course, the store wasn’t the issue.

On the second day, I got on a call with my project mentors and after 2 hours of trying different annotations for Configuring the Bean, my mentor realized that I had messed up the @ComponentScan annotation on the main class. So this is what it originally said:

@ComponentScan(basePackages = { “com.example.demo.*” })

So this basically means that I was telling Spring Boot to “look for (scan) components and create beans of those found in the sub-packages of the default com.example.demo package.” This is why it tried to create a Bean of the DemoController class in the first place. Because it was able to scan that package and identify that it had to create a Bean for this Component marked with @RestController.

However, if you notice, our DemoConfig class that was marked with @Configuration is under the base package. If I hadn’t written any @ComponentScan annotation, it would’ve by default be made to search through the entire application (base package and all its sub-packages). However, for some reason, I decided to include the @ComponentScan annotation explicitly. When I did that, I stopped Spring Boot to search for any Components or Configuration in the base package and made it scan only in the sub-packages.

What were the fixes?


  1. Removing “.*” in the @ComponentScan basePackages array that I had declared. So it would be:

    @ComponentScan(basePackages = {“com.example.demo”})
    
  2. Move the DemoConfig class to a config package under the demo package and keeping the @ComponentScan as it is. So the DemoConfig class would have the following package:

    package com.demo.example.config;
    // Imports
    @Configuration
    public class DemoConfig {
     …
    }
    
  3. Remove the @ComponentScan entirely and keep the class files as it is. This would force Spring Boot to scan for Components in all the packages, including the base and sub-packages.

Did you find this article valuable?

Support Jimil Shah by becoming a sponsor. Any amount is appreciated!