Why is constructor injection preferred over field injection?

Asked constantlyjunior1–8 yrs9 min read

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 a final field — a final field 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 @InjectMocks and ReflectionTestUtils are 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 @Autowired fields 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:

Compiled and run on this build
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"));
Output
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:42

new 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 final field 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:

Compiled and run on this build
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));
Output
constructor injection:
  circular dependency: Alpha → Beta → Alpha
field injection:
  wired, no complaint
  alpha.beta.alpha == alpha ? true

Both 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:

Compiled and run on this build
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());
}
Output
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 null

One 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 interviewsReality
"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?

AnswerField 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?

AnswerConstructor 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?

AnswerA 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

TierExerciseTime
Warm-upBuild both containers10 min
ChallengeMake the cycle impossible20 min
ProductionThe bean that was null for one endpoint40 min
InterviewFull round replay10 min

Practice ladder

Reading this page is not knowing it. Four tiers, ending in a real incident.

Where this question goes next

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.