Constructor injection, and why not field injection
The dependency-injection lesson introduced constructor injection and said to prefer it over field injection. This best-practices lesson makes the case fully, because it is one of the most common things a Spring code review flags, and knowing why — not just "the reviewer said so" — is what makes you write it by default. The short version: use constructor injection; do not use field injection. The rest is the reasoning.
The three ways to inject, and the recommendation
Spring can inject dependencies three ways:
// 1. CONSTRUCTOR injection — the recommendation
@Service
public class ParcelService {
private final ParcelRepository repository;
public ParcelService(ParcelRepository repository) { this.repository = repository; }
}
// 2. FIELD injection — common in tutorials, flagged in reviews
@Service
public class ParcelService {
@Autowired private ParcelRepository repository;
}
// 3. SETTER injection — occasionally justified for genuinely optional dependencies
@Service
public class ParcelService {
private ParcelRepository repository;
@Autowired public void setRepository(ParcelRepository r) { this.repository = r; }
}
Constructor injection is the recommended default (Spring's own documentation says so). Setter injection has narrow uses (an optional dependency that can be reconfigured). Field injection should be avoided. Here is why constructor injection wins on every axis that matters.
Why constructor injection
finalfields — immutable and guaranteed set. A constructor-injected dependency can befinal: set once, never reassigned, thread-safe, and impossible to be null (the object cannot be constructed without it). Field injection cannot usefinal(the field is set after construction by reflection), so the field is mutable and can be null between construction and injection.- Dependencies are explicit and visible. The constructor lists everything the class needs, right there.
You cannot hide a dependency. And this gives a free design signal: if a constructor has eight
parameters, the class has too many responsibilities — the fat constructor makes the problem visible.
Field injection hides this (you can
@Autowiredtwenty fields and never notice the class is a monster). - The object is always valid. A constructor-injected object cannot exist in a half-built state — it has
all its dependencies the moment it exists. Field injection creates the object first and injects later, so
there is a window where it is incomplete (a source of subtle NPEs, e.g. if a
@PostConstructor another injection path uses a not-yet-injected field). - Testable without Spring. You can
new ParcelService(mockRepository)in a plain unit test — no container, no reflection (the unit-tests lesson relied on exactly this). Field injection forces you to use Spring's machinery or reflection hacks to set the field, even for a simple unit test.
Every one of these is a real, practical benefit; together they are why constructor injection is not a style preference but the correct default.
The clincher: it is less code, and needs no @Autowired
The objection to constructor injection used to be verbosity. Modern Spring removes it: a class with a
single constructor needs no @Autowired annotation at all — Spring uses that constructor automatically.
So:
@Service
public class ParcelService {
private final ParcelRepository repository;
public ParcelService(ParcelRepository repository) { // no @Autowired needed
this.repository = repository;
}
}
is less annotated than the field-injection version (which needs @Autowired on each field). With a
record-like boilerplate reducer such as Lombok's @RequiredArgsConstructor, the constructor is generated
from the final fields and you write even less. So constructor injection is now the terser option as well
as the safer one — the old excuse for field injection is gone.
Why field injection is actively bad
To be concrete about what field injection costs, beyond "not as good":
- You cannot make the field
final— losing immutability and the guarantee it is set. - Hidden dependencies — nothing forces them into a visible list; a class accretes them unnoticed.
- Circular dependencies are hidden until runtime — constructor injection fails fast at startup on a circular dependency (A needs B needs A), forcing you to fix the design; field injection may quietly create a half-wired cycle that breaks later.
- Hard to test — you cannot construct the object with its dependencies without Spring or reflection.
Field injection's only real "advantage" is a few less characters, which modern constructor injection has
erased. So the rule stands, and it is one to apply reflexively: constructor injection with final fields,
no @Autowired needed; never field injection. When you see @Autowired on a field in a review, that is
the flag.
Check your work
The recommendation. Constructor injection is the default (Spring's own guidance); setter injection for genuinely optional deps; avoid field injection.
Why constructor injection. final fields (immutable, non-null, thread-safe); explicit visible
dependencies (a fat constructor reveals a fat class); always-valid object (no half-built state); unit-testable
without Spring (new Service(mock)).
Less code now. A single-constructor class needs no @Autowired (Spring uses it automatically); with
Lombok @RequiredArgsConstructor, even less — the verbosity objection is gone.
Why field injection is bad. No final, hidden dependencies, circular deps hidden until runtime (vs
constructor injection failing fast at startup), hard to test — for only a few saved characters.
Practice
- Write a service with constructor injection and
finalfields, and confirm it needs no@Autowired(one constructor). - Rewrite it with field injection and list, concretely, what you lost (final-ness, visibility, easy testing).
- Unit-test the constructor-injected version with
new Service(mock)— no Spring; try the same with the field-injected version and feel the friction. - Create a circular dependency (A needs B, B needs A) with constructor injection and observe it fail fast at startup; reason about why that is better than a hidden runtime cycle.
- Give a class six constructor dependencies and reflect on what that fat constructor tells you about the class's responsibilities.
- Find
@Autowiredon a field in code you can access and refactor it to constructor injection.
Official documentation
- Spring — Constructor-based dependency injection — And why it is recommended.
- Spring Boot — Single-constructor classes need no @Autowired — The terseness point.
- Spring blog — Why field injection is evil (community) — The trade-offs, from the reference.
Next: configuration and profiles, done right.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship