Which Injection Type is Recommended and Why?
4. Field Injection - not recommended for production code. Acceptable in tests (test classes are often not tested for DI patterns). 5. > 5 parameters -> violation of SRP 6. Const...
Junior Level
Recommended: Constructor Injection!
// Correct
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repo;
}
// Incorrect
@Service
public class OrderService {
@Autowired private OrderRepository repo; // Field injection
}
Why:
- Can make field
final - Application will crash immediately if dependency is missing
- Easy to test without Spring
Middle Level
Advantages of Constructor Injection
1. Immutability:
private final Repo repo; // Cannot be changed after creation
2. Fail-fast:
Constructor: error on startup if dependency is missing
Field: object is created, NPE on first method call
3. Testing:
// Constructor: test without Spring
OrderService service = new OrderService(mockRepo);
// Field: need @SpringBootTest or reflection
4. Cycles:
Constructor -> error immediately -> fix architecture
Field -> works -> bugs at runtime
How Spring Chooses a Constructor
// Single constructor -> Spring uses it automatically
public Service(Repo repo) { } // @Autowired not needed!
// Multiple constructors:
// 1. Looks for @Autowired
// 2. If none -> default constructor
// 3. If none -> error
The Cycle Problem
// A -> B -> A via constructors
// Spring cannot create any of them -> error
// Refactoring: extract a common service C
// A -> C, B -> C (no cycle!)
// Workaround: @Lazy
public Service(@Lazy Dependency dep) { }
// -> Spring injects a proxy, real dependency on first call
When Constructor Injection is NOT Optimal
- Too many dependencies (10+) - a sign the class does too much (SRP violation). Break into smaller classes.
- Circular dependencies - Spring cannot create beans. Use setter injection or @Lazy.
- Inheritance with mandatory dependencies - child class must pass dependencies to parent via
super().
Senior Level
Conflict with CGLIB Proxy
Spring AOP creates a proxy via subclass:
1. new Service(...) -> real object
2. CGLIB subclass -> proxy
-> Constructor executes TWICE!
-> Heavy logic in constructor = problem
Solution:
-> Constructor = only field assignment
-> Initialization -> in @PostConstruct
Under the Hood: How Spring Injects
AutowiredAnnotationBeanPostProcessor:
1. Found constructor with @Autowired (or the only one)
2. Resolved dependencies via BeanFactory
3. Called constructor
-> Reflection -> overhead
-> Constructor Injection is slightly faster at startup (one constructor vs all fields). In practice the difference is minimal, the main reason is safety and testability.
Production Experience
Real scenario: Field Injection hid 20 cycles
- Application started but crashed randomly
- 4 hours of debugging -> found circular dependencies
- Switched to Constructor Injection -> error on startup -> fixed in 10 minutes
Best Practices
- Constructor - always by default
- @RequiredArgsConstructor - Lombok for convenience
- @Lazy - not only for circular dependencies. Also useful for lazy loading of heavy beans (saves memory and startup time).
- Field Injection - not recommended for production code. Acceptable in tests (test classes are often not tested for DI patterns).
- > 5 parameters -> violation of SRP
- Constructor = only field assignment
Summary for Senior
- Constructor = immutability, fail-fast, tests
- Field = technical debt
- Cycles -> Constructor breaks immediately (good!)
- CGLIB -> constructor called twice
- @Lazy -> proxy stub, real one on call
- SRP -> > 5 dependencies = split the class
Interview Cheat Sheet
Must know:
- Recommended type is Constructor Injection: immutability (
final), fail-fast, convenient testing without Spring - Constructor Injection makes dependencies explicit - you can see what a class needs
- If a dependency is missing, the application crashes on startup, not at runtime (NPE)
- Testing without Spring:
new Service(mockRepo)- fast and simple - Circular dependencies via constructor break startup - this is good, the problem is visible immediately
- Constructor should only contain field assignment, all logic goes in
@PostConstruct @Lazyresolves cycles via proxy stub, but it is a workaround - refactoring is better-
5 constructor parameters -> violation of SRP, split the class
Common follow-up questions:
- Why is @Lazy a workaround? It masks circular dependencies without eliminating the architectural problem.
- Why is the constructor executed twice with CGLIB? Spring creates the real object + a proxy subclass, both call the constructor.
- How does Spring choose a constructor? If there is one constructor - automatically. If multiple - looks for
@Autowired. - When is Constructor Injection NOT optimal? Too many dependencies (10+), cycles, inheritance with mandatory dependencies.
Red flags (DO NOT say):
- “Field Injection is acceptable in production code” (not recommended, only in tests)
- “@Lazy is the right solution for cycles” (workaround, refactoring is better)
- “The more dependencies, the better - the class is more powerful” (SRP violation, split into parts)
- “Constructor Injection is slower because of Reflection” (difference is in milliseconds, the main point is safety)
Related topics:
- [[01. What is Dependency Injection]]
- [[02. Difference between constructor setter and field injection]]
- [[06. What is Bean Lifecycle]]
- [[07. What are the Bean Lifecycle stages]]
- [[09. What do PostConstruct and PreDestroy methods do]]