DEV Community

Cover image for You should stop using Spring @Autowired
Felix Coutinho
Felix Coutinho

Posted on • Edited on

You should stop using Spring @Autowired

Or "Why you shouldn't use Field Injection when using Spring".


*TL;DR *
Injecting beans directly into fields using @Autowired makes your dependencies "hidden" and encourages bad design. Use constructor-based injection instead.


It is very likely that you have already seen an @Autowired annotation when coding in a Java/Spring application. However, what most of my friends developers don't know is that auto-wiring is one of the most commonly seen anti-patterns in codebases that use the Spring IoC/DI Framework.

Just to refresh your memory here is a short example of what I'm talking about.



@Service
public class UserService {

    @Autowired // Field Injection
    private UserRepository userRepository;

    // ...
}


Enter fullscreen mode Exit fullscreen mode

It may seem like a simple and pragmatic way to do Dependency Injection, right? But the consequences of this can turn your codebase slowly (or fast) into spaghetti.

But why do developers keep choosing this approach? Possibly because it's easy to use and provides an out-of-the-box experience when a developer needs to inject a dependency into a class. However, similar to any other bad practice, this is very easy to replicate across the rest of the codebase, and usually people will never argue why that code design choice was made. And what's the result of using @Autowired? Your code will suffer from coupling, a bad test experience, hidden dependencies, and others.

In my perspective, the absence of comprehension of the underlying mechanics of a system can potentially result in disastrous codebases. This is applicable to the usage of '@Autowired' when using Spring. Without a thorough grasp of crucial concepts such as Inversion of Control and Dependency Injection, developers are more likely to make errors and become ensnared in this pitfall.

To gain a better understanding of code design best practices, it's important for developers and architects to review key concepts and explore different approaches to Dependency Injection with Spring. By doing so, we can evaluate which method is optimal for our needs, and potentially uncover the drawbacks of using @Autowired as a dependency injection strategy.

Dependency Injection with Spring

Dependency Injection is a crucial pattern in software development and is present in production-ready frameworks like Spring. It promotes loose coupling between classes, improves testability, and facilitates modularity.

Image description

There are basically three ways to do Dependency Injection using the Spring Framework:

  • Field Injection uses reflection to set the values of private attributes/fields.
  • Setter Injection uses public setters to set the value of the attributes/fields.
  • Constructor Injection happens at the time of creating the object itself.

Field Injection

Field injection can be achieved using Spring by adding the @Autowired annotation to a class field. And what does @Autowired do? @Autowired is an annotation provided by Spring that allows the automatic wiring of the dependency. When applied to a field, method, or constructor, Spring will try to find a suitable dependency of the required type and inject it into the target class.

@Autowired alone is not the root of all evil. The key aspect is WHERE you are using @Autowired. Most of the time, developers use it at the field/attribute level. Using it at the setter method can reduce the damage, but not eliminate all of it at all.

But why @Autowired is so bad? Basically, @Autowired violates some good code design principles.

Dependency Inversion (D from SOLID)

If you want to use your class outside the application container, for example for unit testing, you are forced to use a Spring container to instantiate your class as there is no other possible way (but reflection) to set the @Autowired fields.

When using field-based dependency injection with @Autowired, the class is inherently hiding these dependencies from the outside world. In other words, no point of injection exists.

Below you can see an example of this scenario.



@Component
public class Calculator {

   @Autowired 
   private Multiplier multiplier;

   public int add(int a, int b) {
      return a + b;
   }

   public int multiply(int a, int b) {
      return multiplier.multiply(a, b);
   }
}

@Component
public class Multiplier {
   public int multiply(int a, int b) {
      return a * b;
   }
}



Enter fullscreen mode Exit fullscreen mode

Despite the fact of in runtime Spring will inject the dependency (an instance of Multiplier) there is no way to inject the dependency manually, for instance when unit testing this class.

The below unit test class will compile, but at runtime it's a good candidate to throw a NullPointerException, why? Because the dependency between Calculator and Multiplier was not satisfied.



public class CalculatorTest {

   @Test
   public void testAdd() {
      Calculator calculator = new Calculator();
      int result = calculator.add(2, 3);
      assertEquals(5, result);
   }

   @Test
   public void testMultiply() {
      Calculator calculator = new Calculator();
      int result = calculator.multiply(2, 3);
      assertEquals(6, result);
   }
}



Enter fullscreen mode Exit fullscreen mode

Clean Code / Screaming Architecture / Single Responsibility Principle (S from SOLID)

Code must scream its design. And the SRP principle states that a class should have only one reason to change. This means that a class should have only one responsibility or concern. @Autowired annotation, in itself, does not violate this principle.

However, if a class has multiple responsibilities and dependencies injected through the @Autowired annotation, it could be a clear sign of a violation of the SRP. In this case, it might be better to refactor the class and extract the responsibilities into separate classes.

Otherwise, if constructor-based dependency injection is used instead, as more dependencies are added to your class, the constructor grows bigger and bigger and the code starts to smell, sending clear signals that something is wrong. This is described in the Clean Code / Screaming Architecture pattern in the book written by Robert C. Martin aka Uncle Bob.

Complexity

@Autowired can make the code more complex, especially when dealing with circular dependencies. When two or more classes depend on each other, it becomes hard to determine the order in which they should be instantiated. This can lead to runtime errors that are hard to debug.


Most likely your project doesn't need @Autowired at all


What should you use instead?

The short answer is Constructor Injection.

In OOP we create an object by calling its constructor. If the constructor expects all required dependencies as parameters, then we can be 100% sure that the class will never be instantiated without having its dependencies injected.

Below an example using Constructor-based Dependency Injection instead of @Autowired.



@Component
public class Calculator {

   private Multiplier multiplier;

   // Default constructor. It will be used by Spring to 
   // inject the dependencies declared here.
   public Calculator(Multiplier multiplier) {
      this.multiplier = multiplier;
   }

   public int add(int a, int b) {
      return a + b;
   }

   public int multiply(int a, int b) {
      return multiplier.multiply(a, b);
   }
}

@Component
public class Multiplier {
   public int multiply(int a, int b) {
      return a * b;
   }
}

public class CalculatorTest {

   @Test
   public void testAdd() {
      Calculator calculator = new Calculator(mock(Multiplier.class));
      int result = calculator.add(2, 3);
      assertEquals(5, result);
   }

   @Test
   public void testMultiply() {
      Multiplier mockMultiplier = mock(Multiplier.class);
      when(mockMultiplier.multiply(2, 3)).thenReturn(6);
      Calculator calculator = new Calculator(mockMultiplier);
      int result = calculator.multiply(2, 3);
      assertEquals(6, result);
   }
}



Enter fullscreen mode Exit fullscreen mode

Now the dependency between the two components was explicitly declared and the code design allows a mock instance of Multiplier to be injected at runtime.

Also, Constructor injection helps in creating immutable objects simply because a constructor is the only possible way to create objects. Once we create a bean, we cannot alter its dependencies anymore. On the other hand, by using setter injection, it’s possible to inject the dependency after creation or change the dependency, thus leading to mutable objects which, among other things, may not be thread-safe in a multi-threaded environment and are harder to debug due to their mutability.

By explicitly defining the dependencies of the class on the constructor, you can make the code more maintainable, testable, and flexible.

  • Maintainable because you rely only on OOP concepts to inject your class dependencies and any change to the class dependencies contract will be communicated to the other class with no effort.
  • Testable because you have a point of injection that will allow the unit tests to pass, for instance, a mock object instead of a concrete implementation. Besides, we make this class immutable by allowing only injections at the constructing time.
  • Flexible because you still able to add more dependencies to the class with minimum effort by adding more parameters to the default constructor.

This approach makes it easier to understand and track the dependencies of your classes. Additionally, it makes it easier to test the class in isolation by forcing your to explicitly providing mock objects for the required dependencies at coding time.

Setter Injection

One more method to do Dependency Injection using Spring is by using setter methods to inject a instance of the dependency.

Below you can see an example of how Setter Injection looks like when using Spring.



public class UserService {
    private UserRepository userRepository;        

    ...

    @Autowired // Setter Injection
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    ...
}



Enter fullscreen mode Exit fullscreen mode

This approach can lead to problems such as:

  • Optional dependencies. Setter Injection allows for optional dependencies, which can lead to null pointer exceptions if the dependencies are not properly checked for null values.
  • Incomplete object state. If an object is partially constructed and a setter is called, it can result in the object being in an incomplete state, which can lead to unexpected behavior or null pointer exceptions again.
  • Hidden dependencies. As the dependency is not explicitly declared in the constructor, making it harder to understand the code and its dependencies.

Despite the drawbacks, Setter Injection sits somewhere between the Field and the Constructor Injection because at least we have a public method that allows developers to inject mocks instead of real implementation making the class easier to test.

Further Considerations

The @Autowired annotation can be omitted from codebases using Spring Framework 4.3 or higher and Spring will use the default constructor and inject all necessary dependencies for you when the class is being managed by the Application Context.. (https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-vs-setter-injection)

So, since the Spring team decided @Autowired should be optional to the default constructor. We can conclude since it's not helping the Spring framework to make a decision, its presence is just noise. Get rid of it!

The below comment from the Spring Framework development team gives one more reason to choose Constructor Injection as your default injection method.

"Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state." and "Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class."

Basically, what they are saying is by using constructor injection you can guarantee the ready-to-use state of the class. If you are using @Autowired or even setter injection your class instance can be instantiated by Spring in a bad state which can cause unexpected behaviour, such as NullPointerException.

Why @Autowired even exist?

If you need to inject optional or changeable dependencies, you may want to use @Autowired to mark the setter method (or a non-default constructor) as the point of injection. But it only makes sense if your dependency is optional or changeable and your implementation can handle the absence of that dependency (nulls).

If several constructors are available and there is no primary/default constructor, at least one of the constructors must be annotated with @Autowired to instruct Spring which one to use. This is a scenario where @Autowired can be considered.

Conclusion

At the end of the day, @Autowired can be useful for small, simple projects, but it is not a good choice for large, complex, and production-ready projects where the code design should be open for extension, flexible, and easy to test. Instead, the recommendation is to use explicit Dependency Injection via class constructor. By doing so, you can ensure that your code remains robust and adaptable.

Something that you and your team will gain for free is that the rest of the project will implicitly reflect this code design decision. Sometimes, developers never learn about proper Dependency Injection, class contracts, or even how to test code effectively simply because they use @Autowired and never question its real necessity and applicability.

Additional reading and references

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-vs-setter-injection
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation
https://betulsahinn.medium.com/why-is-autowired-annotation-not-recommended-4939c46da1f8
https://blog.marcnuri.com/field-injection-is-not-recommended
https://reflectoring.io/constructor-injection/
https://kinbiko.com/posts/2018-02-13-spring-dependency-injection-patterns/
https://blog.frankel.ch/my-case-against-autowiring/
https://www.linkedin.com/pulse/when-autowire-spring-boot-omar-ismail/?trk=articles_directory
https://eng.zemosolabs.com/when-not-to-autowire-in-spring-spring-boot-93e6a01cb793
https://stackoverflow.com/questions/41092751/spring-injects-dependencies-in-constructor-without-autowired-annotation

Top comments (7)

Collapse
 
ntleachdev profile image
NTLeachDev

I agree with the overall sentiment of the article, constructor injection is king. However, even though newer Spring versions do not require @Autowired if there is a single constructor, I do like the explicitness of still including it. It "screams" that "this is the injection point", and also makes the constructor stand out more, imo.

Collapse
 
felixcoutinho profile image
Felix Coutinho

Hello @ntleachdev thank you for your comment. I believe that if the annotation is added to the constructor and not the field, the result s should be the same as described in the article. Again, the major problem is not Autowired but how and where people use Autowired. I understand the concern about labeling the constructor as the point of injection, however it can be perceived as duplicating code or verbose. Wishing you success in your coding endeavors!

Collapse
 
emmysteven profile image
Emmy Steven • Edited

Your post was very insightful, I must say.

What’s your take on the use of Lombok’s @RequiredArgsContructor

Collapse
 
felixcoutinho profile image
Felix Coutinho

Hi @emmysteven thanks!

I believe that annotation satisfies the recommendation of having a default constructor with the required dependencies. You probably are good to go.

BTW I invite you to read one of my other articles about Lombok, called Lombok: The Good, The Bad, and The Controversial

Collapse
 
j_a_o_v_c_t_r profile image
João Victor Martins

Great article, my friend =)

Collapse
 
alexts1993 profile image
Alex Ci

Most of the time I use @Reaource instead of@Autowired

Collapse
 
felixcoutinho profile image
Felix Coutinho

Hi @alexts1993

I think that @resource is part of Jakarta EE and functions similarly to Autowired.

However, the crucial factor is not which annotation you use, but rather where you use it. It's best to prioritize constructor-based injection over field injection, as explained in the article.

Keep up the good coding!