Question 2 · Section 5

Difference between Constructor, Setter and Field Injection

4. Field - DO NOT use 5. > 5 dependencies -> split the class 6. @Lazy - workaround for cycles

Language versions: English Russian Ukrainian

Junior Level

Constructor - dependency comes through the constructor. Setter - dependency comes through a method. Field - dependency comes directly into the field.

// Constructor
public class Service {
    private final Repo repo;
    public Service(Repo repo) { this.repo = repo; }
}

// Setter
public class Service {
    private Repo repo;
    @Autowired public void setRepo(Repo repo) { this.repo = repo; }
}

// Field
public class Service {
    @Autowired private Repo repo;  // Hidden dependency!
}

Recommendation: Use Constructor Injection by default. Setter - for optional dependencies (@Autowired(required = false)). Field Injection - only in tests.


Middle Level

Constructor Injection (The Gold Standard)

// Advantages:
// 1. final fields -> immutability
// 2. Object is fully constructed -> no NPE
// 3. Tests without Spring: new Service(mockRepo)
// 4. Cycles visible immediately -> error on startup

Field Injection (Anti-pattern)

// Problems:
// 1. Hidden dependencies -> cannot see what the class needs
// 2. Cannot be final -> can lose a dependency
// 3. Tests require Spring context
// 4. Cycles are masked -> NPE at runtime

Setter Injection (Rarely)

// When to use:
// 1. Optional dependencies
// 2. Hot-swap at runtime (rare!)

@Autowired(required = false)  // Optional
public void setRepo(Repo repo) { this.repo = repo; }

Initialization Order

1. Constructor -> Constructor Injection
2. Object creation
3. Fields -> Field Injection
4. Setters -> Setter Injection
5. @PostConstruct

-> In constructor, Field and Setter dependencies are still NULL!

When Setter Injection is Justified

  1. Optional dependencies: @Autowired(required = false) - if the dependency is not provided, the bean will still be created
  2. Reconfiguration: need to change a dependency after creation (rare, usually an anti-pattern)
  3. Spring Boot ConfigurationProperties: Spring Boot itself uses setter injection for configuration binding

Senior Level

AutowiredAnnotationBeanPostProcessor

Spring uses BPP for DI:
  1. Scans fields with @Autowired
  2. Scans constructors
  3. Scans setters
  4. Injects dependencies

> Spring 4.x+: a single constructor does not require @Autowired. Spring 6+: constructor injection without annotations is recommended.

-> Reflection API -> overhead
-> Constructor Injection is slightly faster (Spring scans one constructor instead of all fields in the class). In practice the difference is measured in milliseconds at startup.

Circular Dependencies

Constructor Injection:
  -> A needs B, B needs A
  -> Spring cannot create either -> error immediately

Field Injection:
  -> Spring creates A and B via default constructor
  -> Then fills fields
  -> Masks an architectural problem!

Conflict with CGLIB Proxy

Spring creates a proxy via subclass -> calls the constructor
-> If the constructor is heavy -> it will execute twice!
-> The constructor should be simple: only field assignment
-> All logic -> in @PostConstruct

Lombok @RequiredArgsConstructor

@Service
@RequiredArgsConstructor
public class Service {
    private final Repo1 repo1;
    private final Repo2 repo2;
    // Lombok will create the constructor itself!
}

Production Experience

Real scenario: 10 dependencies in constructor

  • Class does too much -> violation of SRP
  • Solution: split into 3 services
  • Result: readability + testability

Best Practices

  1. Constructor - by default (99%)
  2. @RequiredArgsConstructor - Lombok for boilerplate
  3. Setter - only optional dependencies
  4. Field - DO NOT use
  5. > 5 dependencies -> split the class
  6. @Lazy - workaround for cycles

Summary for Senior

  • Constructor = immutability, fail-fast, tests
  • Field = hidden dependencies, hard to test
  • Setter = optional dependencies
  • Cycles -> Constructor breaks immediately
  • CGLIB -> constructor called twice (object + proxy)
  • Lombok -> @RequiredArgsConstructor removes boilerplate

Interview Cheat Sheet

Must know:

  • Constructor injection - dependency through constructor, provides final fields and immutability
  • Setter injection - through a method, suitable for optional dependencies (@Autowired(required = false))
  • Field injection - through a field with @Autowired, anti-pattern: hidden dependencies, impossible to make final
  • Initialization order: constructor -> fields (field injection) -> setters -> @PostConstruct
  • Constructor injection is fail-fast: error on startup if dependency is missing
  • Circular dependencies via constructor break startup, via field - mask the problem
  • Spring 6+ recommends constructor injection without annotations (if there is a single constructor)

Common follow-up questions:

  • Why is field injection an anti-pattern? Hidden dependencies, cannot use final, NPE at runtime, hard to test without Spring.
  • When is setter injection justified? Optional dependencies, Spring Boot ConfigurationProperties binding, hot-swap (rare).
  • Why is CGLIB proxy constructor called twice? The proxy is created via a subclass - first the real object’s constructor, then the proxy’s constructor.
  • What does AutowiredAnnotationBeanPostProcessor do? Scans @Autowired on fields, constructors and setters, injects dependencies via Reflection.

Red flags (DO NOT say):

  • “Field Injection is the most convenient, less code” (convenience != quality, it is technical debt)
  • “Setter injection is fine for mandatory dependencies” (can forget to call, object in incomplete state)
  • “Circular dependencies are not a problem, Spring handles them” (Spring masks, but bugs at runtime)
  • “Lombok completely solves the constructor boilerplate problem” (it does, but does not remove the >5 dependencies problem)

Related topics:

  • [[01. What is Dependency Injection]]
  • [[03. Which injection type is recommended and why]]
  • [[04. What is a Bean in Spring]]
  • [[06. What is Bean Lifecycle]]
  • [[07. What are the Bean Lifecycle stages]]