Hello! Welcome to your next lesson on backend development with Spring.
In our last lesson, we saw how Spring can automatically discover and create beans for us using component scanning and stereotype annotations like @Service and @Repository. We now have a powerful way to get our application's components into the Spring IoC container.
But creating beans is only half the story. A UserService isn't very useful if it can't talk to a UserRepository. The next logical step, and the focus of today's lesson, is to connect these beans together. This process is called Dependency Injection (DI).
This lesson directly addresses the learning outcome: Implement constructor-based and setter-based dependency injection. We will explore how to tell Spring to "wire" one bean into another, making your application components work together seamlessly.
1. The Core Idea of Dependency Injection
You've already been introduced to the concept of Inversion of Control (IoC), where the framework (Spring) takes control of creating and managing your objects. Dependency Injection is the primary pattern used to achieve IoC.
Instead of a class creating its own dependencies, like this:
public class UserService {
// UserService is responsible for creating its own dependency.
private UserRepository userRepository = new UserRepositoryImpl();
// ... business logic
}
The dependencies are "injected" from an external source (the Spring container):
public class UserService {
// The dependency is provided from the outside.
private final UserRepository userRepository;
// The constructor accepts the dependency.
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ... business logic
}
This simple shift is incredibly powerful. It decouples your components, making them easier to test and manage. Spring can inject a real UserRepository in production but a mock UserRepository during testing, all without changing the UserService class.
There are three primary ways Spring can inject these dependencies:
- Constructor Injection
- Setter Injection
- Field Injection
Let's explore each one, starting with the most recommended approach.
2. Constructor-Based Dependency Injection
With constructor injection, the Spring container invokes a class's constructor, passing the required dependencies as arguments. This is the most popular and recommended method for injecting mandatory dependencies.
Let's watch a video that provides a clear, practical demonstration.
Spring Tutorial - 4 - Constructor Injection with Annotations
This video by Kody Simpson demonstrates how to implement constructor injection using the @Autowired annotation. He creates two services and shows how Spring injects one into the other when the application starts.
Watch from 06:24 to 11:21. Pay close attention to how the @Autowired annotation is placed on the constructor. Also, note the important point he makes: for injection to work, both the class receiving the injection (ArcadeService) and the dependency itself (GameProviderService) must be Spring beans.
As the video demonstrated, you simply create a constructor that accepts your dependencies. Spring automatically finds the required beans in its ApplicationContext and passes them in when creating the object.
The Magic of Implicit Autowiring
A key best practice to be aware of: If your class has only one constructor, you do not need to add the @Autowired annotation. Spring is smart enough to know that this must be the constructor to use for injection.
This leads to cleaner, annotation-free constructors:
@Service // Marks this class as a Spring bean
public class ArcadeService {
private final GameProviderService gameProviderService; // A mandatory dependency
// No @Autowired needed here because it's the only constructor.
public ArcadeService(GameProviderService gameProviderService) {
this.gameProviderService = gameProviderService;
}
public void startGame() {
String game = gameProviderService.getGameToPlay();
System.out.println(game + " is being started.");
}
}
Why is Constructor Injection recommended?
- Immutability: You can declare your dependency fields as
final, ensuring they are initialized once and cannot be changed later. This makes your beans more robust and thread-safe. - Guaranteed Dependencies: It guarantees that the bean is created with all its mandatory dependencies. The object cannot be instantiated in an incomplete or invalid state.
- Testability: It's very easy to test the class in isolation (without Spring). You can simply call the constructor and pass in mock objects.
3. Setter-Based Dependency Injection
Setter-based injection is an alternative where the container first calls a no-argument constructor to create the bean and then calls setter methods to inject the dependencies. This method is typically used for optional dependencies.
To signal to Spring that a setter method should be used for injection, you annotate it with @Autowired.
Dependency Injection in Java: Spring Boot Tutorial
Let's read a quick overview of setter injection from an article by CodingNomads. It provides a clear code example and explains the role of @Autowired in this context.
Read the section titled 'Setter Injection'. Focus on the structure: a no-arg constructor (which can be the default one provided by Java) and an @Autowired annotated setter method.
Here's a typical example:
@Service
public class CustomerService {
private NotificationService notificationService; // An optional dependency
// The @Autowired annotation tells Spring to call this method
// and inject a bean of type NotificationService.
@Autowired
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void registerCustomer() {
// ... registration logic ...
// Safely use the optional dependency
if (notificationService != null) {
notificationService.sendWelcomeEmail();
}
}
}
Unlike with constructor injection, the @Autowired annotation is mandatory for setter injection. Without it, Spring would just see a regular setter method and ignore it for injection purposes. The main downside is that the dependency is not final, and the object can exist in a state where the dependency has not yet been set.
4. A Quick Look at Field Injection
You will often see a third type of injection in older codebases or online tutorials: field injection. Here, the @Autowired annotation is placed directly on the field itself.
@Service
public class FieldInjectionExample {
@Autowired
private Dependency dependency;
// No constructor or setter for injection
}
While this looks very concise, it is strongly discouraged in modern Spring development.
Why?
- Poor Testability: It's impossible to instantiate this class in a unit test without using Spring or complex reflection. You can't just call a constructor or setter to provide a mock dependency.
- Hides Dependencies: The class's dependencies are no longer part of its public contract (its constructor or public setters). It's not obvious what a class needs to function correctly just by looking at its public API.
- Encourages Bad Practice: It makes it too easy to add many dependencies to a class, which can be a sign that the class is violating the Single Responsibility Principle.
For these reasons, you should always prefer constructor injection.
5. Summary and Comparison
We've covered the three main types of dependency injection. To help you remember which one to use and when, the following article and image provide an excellent summary.
Inversion of Control and Dependency Injection with Spring
The article 'Inversion of Control and Dependency Injection with Spring' from Baeldung offers a great recommendation on how to combine these injection types.
Read the short paragraph at the end of Section 6, 'Setter-Based Dependency Injection'. It provides the official recommendation from the Spring team.
The Spring documentation's recommendation is clear: use constructor injection for mandatory dependencies and setter injection for optional ones.
This image provides a perfect visual summary of the three approaches and their recommended usage.

Test your understanding!
You see the following class in a project.
@Service
public class OrderProcessor {
@Autowired
private PaymentGateway paymentGateway;
@Autowired
private InventoryService inventoryService;
public void processOrder(Order order) {
// ... logic using paymentGateway and inventoryService
}
}
- What type of dependency injection is being used here?
- Based on what you've learned, how would you refactor this class to follow best practices? Assume both dependencies are mandatory for an order to be processed.
Show answer
-
This class is using field injection.
-
To follow best practices, you should refactor it to use constructor injection. This makes the dependencies explicit and mandatory. Since both dependencies are required, a single constructor is the ideal approach. The
finalkeyword can also be added for immutability.@Service public class OrderProcessor { private final PaymentGateway paymentGateway; private final InventoryService inventoryService; // No @Autowired needed as it's the only constructor public OrderProcessor(PaymentGateway paymentGateway, InventoryService inventoryService) { this.paymentGateway = paymentGateway; this.inventoryService = inventoryService; } public void processOrder(Order order) { // ... logic using paymentGateway and inventoryService } }
Conclusion
You have now learned the crucial skill of connecting your Spring beans. Dependency injection is the mechanism that breathes life into your application, transforming a collection of isolated classes into a cohesive, functioning system.
Key Takeaways:
- Dependency Injection (DI) is the process Spring uses to provide a bean with its required dependencies.
- Constructor Injection is the recommended approach for mandatory dependencies. It improves testability and guarantees an object is created in a valid state.
@Autowiredis optional if there's only one constructor. - Setter Injection is suitable for optional dependencies. It requires the
@Autowiredannotation on the setter method. - Field Injection is concise but strongly discouraged due to its negative impact on testing and design principles.
What's Next?
We've seen how to create beans and how to inject them. But how many instances of each bean does Spring create? Does every class that needs a UserRepository get the same instance, or a new one each time? This concept is known as bean scope. In the next lesson, we will explore the most important bean scopes, singleton and prototype, to understand and control the lifecycle of our beans.