Difference between Constructor, Setter and Field Injection
4. Field - DO NOT use 5. > 5 dependencies -> split the class 6. @Lazy - workaround for cycles
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
- Optional dependencies:
@Autowired(required = false)- if the dependency is not provided, the bean will still be created - Reconfiguration: need to change a dependency after creation (rare, usually an anti-pattern)
- 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
- Constructor - by default (99%)
- @RequiredArgsConstructor - Lombok for boilerplate
- Setter - only optional dependencies
- Field - DO NOT use
- > 5 dependencies -> split the class
- @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
finalfields 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 makefinal - 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
@Autowiredon 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]]