In our last lesson, we learned how to declare our classes as Spring-managed components using stereotype annotations like @Component, @Service, and @Repository. You now know how to tell the Spring IoC container what objects to create and manage. The container now holds a collection of these "beans," ready to be used.
This lesson addresses the next logical step: connecting these beans together. We will explore how Spring performs Dependency Injection to automatically "wire" your components, satisfying their dependencies without you needing to write new statements. We will focus on the @Autowired annotation and, most importantly, on the best practices for its use. You'll learn why constructor injection is the industry-standard approach for building robust and testable backend services, a cornerstone of your goal to master enterprise Java development.
1. Autowiring: The "How" of Dependency Injection
Once your beans are in the Spring container, you need a way to inject them into the classes that depend on them. The @Autowired annotation is Spring's primary mechanism for this. When Spring creates a bean (like a controller), it scans the class for @Autowired annotations. For each one it finds, it searches the container for a bean of the matching type and injects it.
There are three primary ways to use @Autowired, corresponding to three different "injection types."
Spring @Autowired Annotation for Dependency Injections
This article from CodingNomads provides a concise introduction to the @Autowired annotation and a clear overview of the three injection types.
First, read the brief introductory section, What is @Autowired?, to get a formal definition. Then, review the section @Autowired Dependency Injection, which shows code examples for field, setter, and constructor injection. Pay attention to the syntax and where the @Autowired annotation is placed in each case.
As you just read, the three types of dependency injection are:
- Field Injection: The annotation is placed directly on the instance variable.
- Setter Injection: The annotation is placed on a public setter method.
- Constructor Injection: The annotation is placed on the class constructor.
While all three achieve the goal of injecting a dependency, they are not created equal. The Spring development team and the broader Java community strongly recommend one approach over the others.
2. Why Constructor Injection is the Recommended Approach
Let's dive into why constructor injection is the preferred method for modern Spring applications. The core reasons revolve around creating code that is more robust, testable, and self-documenting.
Dan Vega's video provides an excellent walkthrough of the three injection types and makes a clear case for constructor injection.
Spring Constructor Injection: Why is it the recommended approach to Dependency Injection?
This video, "Spring Constructor Injection," by Dan Vega, clearly explains the differences between the injection types and why constructor injection is superior.
First, watch the section where he introduces the three types of DI: injection types. Next, watch his explanation of constructor injection. Notice how he uses the final keyword to make the dependency immutable. He also explains that since Spring 4.3, the @Autowired annotation is optional on a single constructor, a key convenience feature. Then, briefly watch his overviews of setter injection and field injection. Pay close attention to the drawbacks he mentions for field injection, particularly regarding immutability (final keyword) and testability.
The problems with field injection, while seemingly minor in simple examples, can lead to significant issues in larger, real-world applications. The following video provides a powerful, step-by-step demonstration of exactly how and why field injection can fail.
Spring Boot Developers Are Doing This Wrong โ (@Autowired Field Injection Problem) โ
This video from CodeSnippet, "Spring Boot Developers Are Doing This Wrong," is a deep dive into the practical problems of field injection.
The presenter walks through five key problems with field injection and shows how constructor injection solves every one of them. NullPointerException Risk: Watch how field injection allows an object to be created in an invalid state, leading to a NullPointerException when a method is called. This section is crucial. Difficult Unit Testing: See how a simple unit test fails with a NullPointerException because the dependency was not injected outside of the Spring context. This highlights a major testing challenge. Hidden Dependencies: The presenter explains how field injection hides a class's dependencies, making the code harder to read and understand. This is about clean design. Breaks Immutability: He demonstrates how a field-injected dependency can be changed (e.g., set to null) after the object is created. This part shows the risk of mutable state. Cannot Use final: This section explains that you cannot make field-injected dependencies final, which is the primary tool for enforcing immutability. The final point on immutability. Finally, watch the solution section, where he refactors the code to use constructor injection and shows how all five problems are elegantly solved. This brings the entire argument together.
To summarize the consensus from the Spring community:
- Field Injection: Avoid. It can lead to
NullPointerExceptions, makes unit testing difficult, and hides a class's true dependencies. Its only widely accepted use case is in test classes for convenience, as Dan Vega demonstrated. - Setter Injection: Use sparingly. It's useful for optional dependencies that can be set after the object is created. However, it makes the object's state mutable.
- Constructor Injection: Strongly recommended. It ensures that required dependencies are available at instantiation, allows dependencies to be marked as
final(enforcing immutability), and makes your classes easier to test and reason about.
A class that declares its dependencies via its constructor makes a clear contract: "To create me, you must provide me with these things."
Constructor Dependency Injection in Spring
This Baeldung article provides a final, authoritative summary of the benefits of constructor injection and the convenience of implicit autowiring.
First, read the section on Implicit Constructor Injection. This reinforces the point that @Autowired is optional on a single constructor. Then, read the "Pros and Cons" section to review the key advantages of testability and enforcing invariants.
3. Handling Ambiguity: @Qualifier
So far, we've assumed Spring can find exactly one bean that matches the type of the dependency. But what happens if you have multiple beans of the same type?
For example, you might have an EmailService interface with two implementations: SmtpEmailService and SendGridEmailService, both marked as @Service.
public interface EmailService {
void sendEmail(String to, String body);
}
@Service
public class SmtpEmailService implements EmailService { /* ... */ }
@Service
public class SendGridEmailService implements EmailService { /* ... */ }
If another class tries to autowire the EmailService, Spring will be confused and throw an exception because it doesn't know which implementation to inject.
@Service
public class NotificationService {
private final EmailService emailService;
// Spring throws an error here: which EmailService to use?
public NotificationService(EmailService emailService) {
this.emailService = emailService;
}
}
The solution is the @Qualifier annotation. You use it to specify the name of the exact bean you want to inject.
Spring @Autowired Annotation for Dependency Injections
The "Autowired Disambiguation" section in the CodingNomads article explains this problem and solution clearly.
Read the entire section Autowired Disambiguation. It shows an example with a VideoCard interface and two implementations, demonstrating how @Qualifier is used with @Autowired to resolve the ambiguity.
When using constructor injection, the @Qualifier annotation is placed on the constructor parameter, right next to the type it's qualifying.
Let's assume our bean names are the default ones (the class name starting with a lowercase letter: smtpEmailService and sendGridEmailService). We can resolve the ambiguity like this:
@Service
public class NotificationService {
private final EmailService emailService;
public NotificationService(@Qualifier("sendGridEmailService") EmailService emailService) {
this.emailService = emailService;
}
}
This tells Spring explicitly: "I need an EmailService, and specifically, I want the one named sendGridEmailService."

Conclusion
You have now mastered one of the most fundamental mechanisms in the Spring Framework. By understanding how to wire components together effectively, you are well on your way to building complex, loosely coupled backend applications.
Key Takeaways:
@Autowiredis Spring's annotation for performing dependency injection.- There are three types of injection: field, setter, and constructor.
- Constructor injection is the recommended approach because it promotes immutability (with
final), guarantees that required dependencies are present, and improves testability. - In classes with a single constructor, the
@Autowiredannotation is optional. Spring handles it implicitly. - When multiple beans of the same type exist, use the
@Qualifierannotation alongside@Autowiredto resolve the ambiguity by specifying the bean name.
In our next lesson, we will examine how Spring manages the lifecycle of these beans by looking at bean scopes. You'll learn the difference between the default singleton scope, where one instance is shared across the entire application, and the prototype scope, where a new instance is created every time it's requested. This is crucial for managing state in your application correctly.