Why is My @Autowired Component Null?

One of the magical things about Spring/SpringBoot is the way we can use the @Autowired annotation to inject components into other components and just use them. Until it doesn’t work. I recently lost hours to a situation where my injected component was inexplicably null. After confirming that my @Autowired class was indeed annotated as a component, I proceeded to be very frustrated for quite some time. My case didn’t match the common scenarios I found via Google. I’ll go through the reasons why our component might not be injected properly and include the odd case that I had encountered.

Setup

Before I dive into things, let’s take a look at the example we’ll be using. The example code is a REST controller that converts hex color codes to RGB values and vice versa. We have a REST controller and a service class that does the conversions. We’re also going to be using basic security.

Our conversion class is called ColorConversionService and is annotated as a @Service:

@Service
public class ColorConversionService {
   // Methods for doing the conversion work
}

We’ll use a basic REST controller and autowire the service dependency:

@RestController
@RequestMapping("/rest")
public class ColorConversionController {
   @Autowired
   private ColorConversionService colorConversionService;

   // Methods for REST endpoints
}

For security, we’ll use a simple in-memory authentication with the super-secure username and password combinations of user/user and admin/admin.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.httpBasic().and().authorizeRequests()
         .antMatchers("/rest").hasAnyRole("USER")
         .and().csrf().disable();
   }

   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.inMemoryAuthentication()
         .withUser("user")
         .password("{noop}user")
         .roles("USER");
      auth.inMemoryAuthentication()
         .withUser("admin")
         .password("{noop}admin")
         .roles("USER", "ADMIN");
   }
}

Two Commonly Given Reasons

When I originally planned this post I was going to start by illustrating, with example code, the two most common reasons I found in my search. However, I’m using the latest Spring Boot release that was available when I started writing this (Spring Boot 2.3.3.RELEASE) and those two reasons aren’t giving me the null failure I was expecting. It seems likely that the Spring team has improved things, but I’ll list them here for those who may be using an older version.

Forgotten Annotation

This is the first thing I actually checked when I was having my mysterious null autowire adventure. It also came up in every possible search combination I tried. However, when I set up my example and left the @Service annotation off my service class, the application failed to start up with a very clear error as to what went wrong.

Manually Instantiating the Service Class

The other reason I found for an injected instance being null, is that the class was instantiated somewhere else in the application using its constructor. The explanation there is that it confuses the Spring Container. I tried various combinations of instantiating my service class manually and in every case, my REST controller still had a non-null ColorConversionService properly injected into it. So it seems that maybe the Spring Container has gotten smarter, but this is still something to check if you’re in this situation. It’s also just possible that I missed the magic combination of manual instantiation and dependency injection to break my code.

The Actual Problem

In my case, the problem I had was related to the Spring Security @PreAuthorize annotation. The controller method where the injected dependency was null was private. It had been working fine, but when I added the @PreAuthorize annotation to the method, the injected object was suddenly null.

Let’s look at what that might look like:

@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/admin/hex-to-rgb/{hex}")
private String convertHexToRgbAdmin(@PathVariable("hex") String hex) {
   return colorConversionService.convertHexToRgb(hex) + " for admins";
}

The solution is simply to mark the method as public:

@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/admin/hex-to-rgb/{hex}")
private String convertHexToRgbAdmin(@PathVariable("hex") String hex) {
   return colorConversionService.convertHexToRgb(hex) + " for admins";
}

Conclusion

This post didn’t turn out quite like I was planning given that I couldn’t prod the first two scenarios into actually giving me a null injected dependency. However, I’m going to leave them for completeness. I do hope that the final scenario will help someone who’s having a similar problem.

To quickly recap, if you’re having weird behavior with an injected dependency, check the following:

  • That your injected class is properly annotated
  • That your injected class hasn’t been manually instantiated via it’s constructor anywhere in the application
  • That your controller method that references the null injected class is not both using @PreAuthorize and marked private

The example code is out on BitBucket.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s