Why is constructor injection preferred over field injection?
Constructor injection makes the dependency list part of the type, so a bean cannot be built wrong, its fields can be final, and a cycle fails immediately. Field injection wires in a second phase after construction, which is why it hides both a missing dependency and a circular one until something calls the method.
The Answer
Say this in the room. 45 seconds.
- Constructor injection puts the dependencies in the signature, so the compiler enforces them. There is no way to obtain a half-wired object.
- That makes the fields
final. Field injection cannot produce afinalfield — afinalfield must be assigned by the end of the constructor, so it is a language rule, not a Spring preference. - Field injection wires in a second phase, after the object already exists. Everything people dislike about it follows from that gap.
- A test can construct the class directly, with plain stubs. Field injection needs reflection to do the same job, which is what
@InjectMocksandReflectionTestUtilsare for. - A circular dependency fails immediately with constructor injection, because there is no order in which the constructors can run. Field injection satisfies the cycle silently.
- And a constructor with nine parameters is uncomfortable to look at, which is the point. Nine
@Autowiredfields are not.
Understand It
The difference is one phase, not one style
A container has two ways to give a bean its collaborators. It can pass them to the constructor — in which case the object does not exist until they are all available. Or it can construct the object first and assign its fields afterwards.
That second phase is the entire difference, and it is why a field-injected class is usable in a state its author never intended:
System.out.println("a field-injected bean, constructed with new:");
try {
new FieldInjectedService().lookup("42");
} catch (NullPointerException e) {
System.out.println(" " + e.getMessage());
}
System.out.println("a constructor-injected bean cannot be built wrong:");
ConstructorInjectedService service = new ConstructorInjectedService(
new JdbcRepository(), msg -> System.out.println(" audit: " + msg));
System.out.println(" " + service.lookup("42"));a field-injected bean, constructed with new:
Cannot invoke "Verify$Repository.find(String)" because "this.repository" is null
a constructor-injected bean cannot be built wrong:
row:42new FieldInjectedService() compiles, runs, and hands you an object that will fail on first use. new ConstructorInjectedService() does not compile at all — you cannot ask for the object without supplying what it needs.
That is the whole argument, and it is a compile-time versus runtime argument rather than a matter of taste.
Why the fields can be final, and why that is not a small thing
@Autowired private final Repository repository; // does not compile
A final field must be assigned by the end of every constructor. Field injection assigns it later, so the code cannot be written: variable repository not initialized in the default constructor. Java forbids it before Spring is involved.
With constructor injection the field is naturally final, which buys three things:
- Nothing reassigns it later, including a future maintainer who adds a setter.
- It is safe to publish across threads. A
finalfield assigned in the constructor is visible to every thread that sees the object, with no synchronisation. A non-final field written after construction has no such guarantee. Since beans are singletons shared by every request, this is a real property and not a formality. - It documents that the dependency is fixed for the object's lifetime.
final does not protect against reflection — setAccessible(true) can still write it, which is exactly how test frameworks wire field-injected beans. It protects against your own code, and it tells the reader something true.
The cycle that field injection hides
Two beans that need each other cannot both be constructed first. Build one and you need the other; build the other and you need the first. Constructor injection therefore has nowhere to start, and can say so:
System.out.println("constructor injection:");
try {
byConstructor(Alpha.class, new LinkedHashSet<>());
} catch (IllegalStateException e) {
System.out.println(" " + e.getMessage());
}
System.out.println("field injection:");
Map<Class<?>, Object> beans = byField(FieldAlpha.class, FieldBeta.class);
FieldAlpha alpha = (FieldAlpha) beans.get(FieldAlpha.class);
System.out.println(" wired, no complaint");
System.out.println(" alpha.beta.alpha == alpha ? " + (alpha.beta.alpha == alpha));constructor injection:
circular dependency: Alpha → Beta → Alpha
field injection:
wired, no complaint
alpha.beta.alpha == alpha ? trueBoth containers in that output are a dozen lines of plain Java, and they disagree because they must. The field-injected one instantiates everything and wires afterwards, so a cycle costs it nothing. The constructor-injected one has to pick an order, and there isn't one.
Spring behaves the same way. It can resolve a field-injected singleton cycle using a cache of early references, and for years it did so quietly. Spring Boot 2.6 made circular references fail at startup by default, because a cycle that works is still a design problem — usually two classes that should be one, or a missing third.
So field injection did not solve the cycle. It postponed the conversation about it.
What a test has to do
The constructor is a test seam you get for free. A stub is one line and needs no framework:
var service = new ConstructorInjectedService(id -> "row:" + id, msg -> {});
For a field-injected class the same test needs reflection, and reflection only wires what you remember to wire. Add a dependency six months later and the gap shows up like this:
FieldInjectedService partiallyWired = new FieldInjectedService();
inject(partiallyWired, "repository", (Repository) id -> "stub:" + id);
System.out.println("lookup, which only touches the repository:");
System.out.println(" " + partiallyWired.lookup("42"));
System.out.println("audit, which touches the field nobody wired:");
try {
partiallyWired.audit("42");
} catch (NullPointerException e) {
System.out.println(" " + e.getMessage());
}lookup, which only touches the repository:
stub:42
audit, which touches the field nobody wired:
Cannot invoke "Verify$Auditor.log(String)" because "this.auditor" is nullOne path works and the other throws, from the same object, because a field-injected bean has no definition of "fully constructed". With a constructor, adding a parameter breaks the build at every call site — which is the cheapest possible moment to find out.
The honest case for the other two
Setter injection has a real use: a genuinely optional dependency, or one that can be reconfigured after startup. That is rare, and "optional" usually means the class is doing two jobs.
Field injection is defensible in a test class, where the container is present, the object is short-lived and nothing else constructs it. @Autowired fields in a @SpringBootTest are normal and fine.
And a fair point against constructor injection: with many dependencies the constructor gets long. The answer is not to hide them — it is that a class needing nine collaborators is telling you something, and a long constructor is the only version of that message you cannot ignore. Lombok's @RequiredArgsConstructor removes the typing without removing the signal.
What Spring actually requires
Since Spring 4.3, a class with one constructor needs no @Autowired at all — the container uses it. So the recommended form is also the shortest:
@Service
public class OrderService {
private final OrderRepository orders;
private final PaymentClient payments;
OrderService(OrderRepository orders, PaymentClient payments) { // no annotation needed
this.orders = orders;
this.payments = payments;
}
}
With two or more constructors you must mark one with @Autowired, or Spring cannot choose.
Interviewer's Next Move
The question after the question. This is what the round is actually testing.
1. "Why is constructor injection preferred?"
The dependencies become part of the type, so the compiler enforces them and a half-wired object cannot exist. The fields can be final, tests need no reflection, and a circular dependency fails immediately instead of being wired silently.
2. "Why can't a field-injected field be final?"
Because a final field must be assigned by the end of the constructor, and field injection assigns it after construction. It is a Java rule — the code does not compile, regardless of framework.
3. "What does field injection actually cost at runtime?"
Nothing measurable. The cost is that the object exists in an unwired state, so a missing dependency, a partially wired test and a circular reference all surface later — as a NullPointerException in whichever method touches the field first.
4. "How does field injection let a circular dependency work?" The container instantiates both beans first and wires them in a second pass, so neither needs the other to exist yet. Constructor injection has no such phase and no valid order, which is why it fails immediately. Boot 2.6 made cycles fail by default anyway, because a resolvable cycle is still a design problem.
5. "Do you need @Autowired on the constructor?"
Not since Spring 4.3, if the class has exactly one constructor. With more than one you must mark the one Spring should use.
6. "Is field injection ever acceptable?" In test classes, where the container is present and nothing else constructs the object. Setter injection has a narrower case: a genuinely optional or reconfigurable dependency.
7. "Your constructor has nine parameters. What do you do?" Not hide them. Nine collaborators means the class has more than one responsibility — split it, or introduce a facade that is itself meaningful. Making the list invisible removes the only signal that was working.
Code traps
Trap A — predict before you run:
@Service
public class ReportService {
@Autowired private TemplateEngine templates;
public ReportService() {
templates.warmUp();
}
}
Answer
NullPointerException at startup, inside the constructor. Field injection happens after construction, so templates is still null while the constructor body runs. This is the failure people describe as "Spring didn't inject my bean" — it injected it a moment later than the code assumed. Move the call to @PostConstruct, or take the dependency as a constructor parameter and the problem cannot be expressed.
Trap B:
@Service
public class A {
@Autowired private B b;
}
@Service
public class B {
@Autowired private A a;
}
Answer
On Spring Boot 2.6 and later this fails at startup with a circular reference error. Before 2.6 it started and worked, because field injection wires in a second phase. Converting both to constructor injection also fails — and that is the useful outcome, because it makes you fix the design rather than the wiring. Setting spring.main.allow-circular-references=true silences the message and keeps the problem.
Common wrong answers
| Said in interviews | Reality |
|---|---|
| "Field injection is slower." | It is not. The cost is when problems surface, not throughput. |
| "Constructor injection is just cleaner code." | It is a compile-time guarantee. The compiler rejects a half-wired object; nothing rejects a null field. |
| "You can make a field-injected field final if you try." | You cannot write it — a final field must be assigned in the constructor. |
| "Circular dependencies are a Spring limitation." | They are an ordering impossibility. Field injection only avoids it by wiring in a second phase. |
"@Autowired on the constructor is required." | Not since Spring 4.3, when the class has one constructor. |
Check Yourself
Q1. Why does calling a dependency from a constructor throw with field injection but not with constructor injection?
Answer
Field injection assigns the fields after the object is constructed, so during the constructor body every injected field is still null. With constructor injection the dependency arrives as a parameter, so it is available before the body runs and the mistake cannot be written.
Q2. Two beans depend on each other. What happens under each strategy, and which do you want?
Answer
Constructor injection fails immediately — there is no order in which both constructors can run. Field injection wires them in a second phase and succeeds, though Boot 2.6+ rejects it by default anyway. You want the failure: it is the same design problem either way, and only one version tells you.
Q3. Why does final on an injected field matter for a singleton bean specifically?
Answer
A singleton is shared by every request thread. A final field assigned in the constructor is safely published — every thread that sees the object sees the field. A field written after construction carries no such guarantee, so the collaborator itself becomes shared mutable state in principle.
Practice
| Tier | Exercise | Time |
|---|---|---|
| Warm-up | Build both containers | 10 min |
| Challenge | Make the cycle impossible | 20 min |
| Production | The bean that was null for one endpoint | 40 min |
| Interview | Full round replay | 10 min |
Practice ladder
Reading this page is not knowing it. Four tiers, ending in a real incident.
- Warm-up10 min
Build both containers
One concept, guided. Near-impossible to fail.
- Challenge20 min
Make the cycle impossible
Edge cases. You have to reason, and two valid fixes differ.
- Production incident40 min
The bean that was null for one endpoint
A real incident: symptom first, cause hidden, tradeoff at the end.
- Interview replay10 min
Full round replay — dependency injection
Timed verbal replay with pass/fail criteria per follow-up.
Where this question goes next
- spring circular dependencies — not written yet
- spring bean scopes — not written yet
- How does Spring AOP work, and what are its limits?
- spring stereotype annotations — not written yet
Every runnable example above was compiled and executed against openjdk 21.0.11 on this build, and its output diffed against what this page claims. Last updated 2026-08-27.