Spring BootSpring Corebeginner
Updated:

Dependency Injection in Spring: Constructor vs Setter vs Field

6 min read

Spring offers three ways to inject dependencies — constructor, setter and field. This guide shows all three, why teams standardise on constructor injection, and the traps.

TL;DR – Quick Answer

Dependency injection in Spring is the technique the container uses to supply a class its dependencies from outside instead of the class creating them. Spring supports three styles: constructor injection (dependencies passed to the constructor), setter injection (passed through setter methods), and field injection (set directly on annotated fields). Constructor injection is the recommended default because it makes dependencies required, final and easy to test.

On This Page

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?
Constructor injection, setter injection and field injection. Constructor injection passes dependencies as constructor arguments, setter injection uses setter methods, and field injection places @Autowired directly on the field. Constructor injection is generally preferred for mandatory dependencies.
Why is constructor injection preferred over field injection?
Constructor injection lets you mark dependencies final and guarantees the object is fully initialized once built, so it can never exist in a half-wired state. It also makes dependencies visible in the constructor signature and easy to supply in unit tests without Spring. Field injection hides dependencies and cannot be made final.
Do I still need @Autowired for constructor injection?
Not if the class has a single constructor. Since Spring 4.3, the container automatically injects dependencies into a lone constructor, so @Autowired is optional there. You only need @Autowired on a constructor when a class declares more than one constructor and you must indicate which to use.
What is the difference between dependency injection and inversion of control?
Inversion of control is the broad principle that the container, not your code, controls object creation. Dependency injection is the specific mechanism that achieves it by supplying dependencies from outside. DI is how Spring implements IoC.
How does Spring know which bean to inject?
By type first: it looks for a bean matching the required type. If several beans match, you disambiguate with @Qualifier or mark one @Primary. If none match a required dependency, the container fails fast at startup with a clear error rather than at runtime.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 15 July 2026 LinkedIn
Chat with us