skip to content

Why does the Spring team recommend constructor injection over field injection for required dependencies?

level: middleimportance: must knowfreq 80%

answer

  1. fail-fast startup vs NPE later
  2. final = immutable + thread-safe
  3. ctor signature = honest contract, exposes God object
  4. new Service(mock) — no context
  5. cycles fail loud vs hidden

basics

~10 s

Constructor injection guarantees required dependencies exist at creation, lets fields be final (immutable), makes dependencies explicit, and lets you build the object in tests with plain new — no Spring or reflection needed.

solid answer

~40 s

Constructor injection is preferred for required dependencies for several reasons. First, it guarantees the object is never created in a half-initialized state: if a dependency is missing, the container fails fast at startup rather than throwing a NullPointerException later. Second, fields can be final, making the bean immutable and thread-safe by construction. Third, dependencies are explicit in the constructor signature — a class demanding eight constructor args visibly signals it does too much (a design smell field injection hides). Fourth, testability: you can instantiate the class with `new MyService(mockDep)` in a plain unit test without a Spring context or reflection. Field injection defeats all of these: no final, hidden dependencies, and you must use reflection or a container to populate fields in tests. Setter injection remains appropriate for genuinely optional dependencies.

code

java · 20 lines
java
// Constructor injection — unit-testable with plain new, no Spring context
class OrderServiceTest {
    @Test
    void placesOrder() {
        PaymentGateway gateway = mock(PaymentGateway.class);
        OrderService service = new OrderService(gateway); // no container needed
        service.place(anOrder());
        verify(gateway).charge(any());
    }
}

// Field injection — same test now needs reflection or a Spring context
class BadServiceTest {
    @Test
    void placesOrder() {
        BadService service = new BadService();
        ReflectionTestUtils.setField(service, "gateway", mock(PaymentGateway.class));
        // brittle: string field name, breaks on rename
    }
}

go deeper

for a junior

Knows constructor injection is recommended but may only cite 'best practice'.

for a middle

Articulates fail-fast, final/immutability, and testability concretely.

for a senior

Connects it to SRP, circular-dependency detection, and thread-safety.

for a principal

Treats the constructor as the class's public contract and a design-pressure signal.

The Spring reference documentation explicitly recommends **constructor injection for mandatory dependencies** and setter injection for optional ones. The reasons: **1. Fail-fast on missing required dependencies.** With constructor injection the container cannot even instantiate the bean unless every parameter resolves. A misconfiguration surfaces as a startup `NoSuchBeanDefinitionException` / `UnsatisfiedDependencyException`, not a runtime `NullPointerException` deep in production. Field injection allows an object to exist with a `null` collaborator. **2. Immutability.** Constructor-assigned fields can be `final`. A `final` reference is set exactly once and never reassigned, which makes the bean **thread-safe** with respect to its dependencies and easier to reason about. Neither setter nor field injection can use `final`. **3. Explicit dependencies / visible design smells.** The constructor signature is the object's honest contract of what it needs. If it grows to many parameters, that's a loud signal the class has too many responsibilities and should be split (**Single Responsibility Principle**). Field injection scatters `@Autowired` across the class and hides the true dependency count, letting a **God object** grow unnoticed. **4. Testability without the container.** A constructor-injected class is trivially unit-tested: ```java var service = new OrderService(mock(PaymentGateway.class)); ``` No `ApplicationContext`, no `@SpringBootTest`, no reflection. Field-injected classes have no way to set private `@Autowired` fields from a plain test except `ReflectionTestUtils.setField(...)` or spinning up a context — slower and more fragile. **5. No circular-dependency hiding.** Constructor injection **fails loudly** on a circular dependency (`BeanCurrentlyInCreationException`), forcing you to fix the design. Field/setter injection can silently paper over cycles (Spring resolves them by setting fields later), leaving a latent design problem. **Counterpoints / when not to.** - **Optional dependencies** → setter injection with `@Autowired(required=false)` or `Optional<T>`/`@Nullable`. - **Legacy frameworks** that require a no-arg constructor may force setter injection. - Historically, unavoidable circular dependencies were sometimes broken by making one side setter-injected — but the modern advice is to redesign instead. **Key APIs.** `@Autowired`, `@Autowired(required=false)`, `UnsatisfiedDependencyException`, `BeanCurrentlyInCreationException`, `ReflectionTestUtils.setField` (test-only workaround for field injection).

  • How does constructor injection help expose a class that violates the Single Responsibility Principle?
    Every dependency must appear as a constructor parameter, so a bloated constructor with many args is an immediately visible signal the class does too much and should be split. Field injection hides this because dependencies are scattered as annotated fields.
  • What happens with a circular dependency under constructor vs field injection?
    Constructor injection fails fast with BeanCurrentlyInCreationException, forcing a redesign. Field/setter injection can silently resolve the cycle by populating fields after construction, hiding the design problem.

saying these in an interview costs you the question

  • 'Field injection is cleaner because there's less boilerplate' — ignores testability/immutability costs
  • Claiming constructor injection can't handle optional dependencies (it can, via @Nullable/Optional)
  • Saying field injection is fine because Spring handles it — misses that it breaks plain unit tests

context