Dependency injection is how Spring actually delivers on the promise of inversion of control. The container does not just decide which object your class needs — it has to hand it over somehow. Injection is that handover, and Spring gives you three ways to receive it.
Knowing all three is table stakes; knowing which to use and why is what separates a fresher who memorised the framework from one who has used it. This page covers constructor, setter and field injection with runnable examples and the reasoning teams use to choose.
The setup: a class with a dependency
Every injection style solves the same problem — give NotificationService an
EmailSender — so let us fix the players first:
public interface EmailSender {
void send(String to, String message);
}
@Component
public class SmtpEmailSender implements EmailSender {
public void send(String to, String message) {
System.out.println("SMTP to " + to + ": " + message);
}
}
SmtpEmailSender is annotated @Component, so it becomes a bean the container manages. Now
NotificationService needs one injected. The three styles differ only in where the
injection point sits.
Constructor injection
Here the dependency arrives as a constructor argument. This is the style you should reach for by default.
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final EmailSender emailSender; // can be final!
// Single constructor: Spring injects automatically, no @Autowired needed
public NotificationService(EmailSender emailSender) {
this.emailSender = emailSender;
}
public void notifyUser(String email) {
emailSender.send(email, "Welcome to CodeBegun");
}
}
Three things make this the strong choice. The field is final, so once the object exists it
can never be re-pointed and the compiler enforces that it was set. The object is never
half-built — you cannot get a NotificationService without its EmailSender. And in a unit
test you just call new NotificationService(fakeSender) with no Spring involved at all.
Since Spring 4.3, a class with exactly one constructor needs no @Autowired — the container
injects into it automatically. That is why modern Spring Boot code often has no @Autowired
anywhere, and it surprises people who learned the older style. The annotation itself is
explored in the @Autowired annotation.
Pro tip: In interviews, when asked "which injection type do you use?", answer "constructor injection" and immediately give the reason: "dependencies become final and required, the object is never in a half-initialised state, and I can construct it in tests without Spring." That reasoning is what interviewers are actually checking for.
Setter injection
Here the container calls a setter after constructing the object. It suits genuinely optional dependencies.
@Service
public class NotificationService {
private EmailSender emailSender; // not final — can change after construction
@Autowired
public void setEmailSender(EmailSender emailSender) {
this.emailSender = emailSender;
}
public void notifyUser(String email) {
emailSender.send(email, "Welcome to CodeBegun");
}
}
The object gets built first, then the setter runs. The upside is flexibility — the
dependency can be reconfigured or left unset. The downside is exactly that: the object can
exist in a state where emailSender is still null, which invites NullPointerException if
someone calls notifyUser too early. Use setter injection when the dependency really is
optional, not as a habit.
Field injection
Here @Autowired sits directly on the field, and Spring sets it by reflection.
@Service
public class NotificationService {
@Autowired
private EmailSender emailSender; // no constructor, no setter
public void notifyUser(String email) {
emailSender.send(email, "Welcome to CodeBegun");
}
}
It is the shortest to type, which is why so much tutorial code uses it — and why so many
codebases regret it. The field cannot be final. The dependency is invisible in the class's
public shape, so you cannot see what the class needs without scanning its fields. Worst,
unit-testing it without Spring means either reflection hacks or a full container, because
there is no constructor or setter to pass a fake through. Most teams treat field injection as
a smell to avoid outside quick prototypes.
The three compared
| Aspect | Constructor | Setter | Field |
|---|---|---|---|
Dependency can be final |
Yes | No | No |
| Object always fully wired | Yes | No | No |
| Easy to test without Spring | Yes | Yes | No |
| Good for optional dependencies | No | Yes | No |
| Recommended as default | Yes | Sometimes | Rarely |
The pattern is clear: constructor injection for required dependencies (almost everything), setter injection for the occasional optional one, field injection reserved for throwaway code. This choice is a direct application of the inversion-of-control principle; if that base is not solid, revisit inversion of control in Spring.
What Spring injects, and how it chooses
The container matches dependencies by type. When it sees a constructor parameter of type
EmailSender, it looks for a bean assignable to EmailSender. With SmtpEmailSender as the
only implementation, that resolves cleanly.
Ambiguity appears when two implementations exist:
@Component
public class SmsSender implements EmailSender { /* ... */ }
// Now TWO EmailSender beans exist — Spring cannot guess which you want
@Service
public class NotificationService {
public NotificationService(@Qualifier("smtpEmailSender") EmailSender sender) {
// @Qualifier names the exact bean to inject
}
}
Faced with two candidates, the container fails fast at startup unless you disambiguate with
@Qualifier (name the bean) or @Primary (mark a default). This fail-fast behaviour is a
feature: wiring mistakes surface the moment the app boots, not deep in production. The beans
being matched here are defined and named following the rules in
Spring beans.
Common mistake: Injecting a concrete class (
SmtpEmailSender) instead of the interface (EmailSender). It works, but you have thrown away the main benefit of DI — the ability to swap implementations. Always inject the interface; let the container decide the concrete bean.
Circular dependencies: a design smell DI exposes
One situation where injection styles behave differently is a circular dependency — bean A
needs B, and B needs A. With constructor injection, Spring cannot build either one first, so
it fails fast at startup with a clear BeanCurrentlyInCreationException. Beginners sometimes
"fix" this by switching to field or setter injection, which lets the app start because the
objects can be created before being wired.
That fix is a trap. The startup failure was telling you something real: two classes depending on each other in a cycle is almost always a design problem, not a Spring problem. The right response is to break the cycle — extract the shared logic into a third class, or rethink which class should own the responsibility.
// Smell: OrderService and InvoiceService each need the other
// Fix: pull the shared behaviour into a third collaborator both depend on
@Service
public class OrderService {
private final PricingCalculator calculator; // both depend on THIS instead
public OrderService(PricingCalculator calculator) {
this.calculator = calculator;
}
}
This is a good example of constructor injection being more helpful precisely because it is stricter. It surfaces the problem at startup instead of letting a tangled design run and fail mysteriously later. Treat a circular-dependency error as a prompt to improve the design, not as an inconvenience to work around.
How DI connects to the wider container
Every injection point discussed here is resolved by the same IoC container that manages your beans. When you write a constructor parameter, you are telling the container to look up a matching bean from its registry and pass it in. That registry is built from the beans you define following Spring beans, and the whole handover is the container fulfilling the inversion-of-control contract.
Understanding that link keeps the topics from feeling like a pile of separate annotations. IoC is the principle, the container is the machinery, beans are the objects it holds, and dependency injection is the moment it connects one bean to another. Every Spring feature you meet later — from web controllers to data repositories — is wired by exactly this mechanism.
Bringing it together
Dependency injection is the mechanism behind Spring's inversion of control: the container supplies your dependencies rather than your class creating them. Constructor injection is the default because it yields final, always-present dependencies and Spring-free tests; setter injection handles the rare optional dependency; field injection is convenient but costs you testability and clarity.
Understand why the industry standardised on constructor injection and you will answer the common follow-ups without effort. Continue through the Spring Boot learning hub into the @Autowired annotation and bean definitions, then pressure-test yourself with the Spring Boot fresher interview questions.
Frequently Asked Questions
What are the three types of dependency injection in Spring?
Why is constructor injection preferred over field injection?
Do I still need @Autowired for constructor injection?
What is the difference between dependency injection and inversion of control?
How does Spring know which bean to inject?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

