Which injection type is recommended and why
The official Spring Framework documentation and modern software engineering consensus unequivocally recommend Constructor Injection for all mandatory application dependencies.
🟢 Junior Level
1. The Official Spring Recommendation
The official Spring Framework documentation and modern software engineering consensus unequivocally recommend Constructor Injection for all mandatory application dependencies.
// ✅ INDUSTRY GOLD STANDARD: Constructor Injection with Lombok
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentGateway paymentGateway;
// Lombok generates a constructor containing all final fields.
// In Spring 4.3+, @Autowired on a single constructor is completely optional.
}
2. The Four Foundational Pillars
- Guaranteed Immutability (
finalfields): Declaring dependencies asprivate finalensures that references cannot be reassigned or mutated after bean instantiation, establishing thread safety. - Compile-Time and Startup Null-Safety: It is physically impossible to instantiate the class in a half-configured state. Both the Java compiler and the Spring IoC container enforce that all required dependencies are supplied at construction time.
- Frictionless POJO Unit Testing:
Classes can be tested in isolation as pure Java objects using standard
new OrderService(mockRepo, mockGateway)constructors without requiring Spring test runners, reflection tools, or Mockito’s@InjectMocks. Tests execute in microseconds. - Fail-Fast Initialization:
If a required dependency is missing from the application context, Spring crashes immediately at startup with
NoSuchBeanDefinitionExceptionorUnsatisfiedDependencyException, rather than producing unexpectedNullPointerExceptions during production runtime traffic.
🟡 Middle Level
1. Architectural Code Smell Detector (SRP Enforcer)
One of the greatest subtle advantages of Constructor Injection is that it acts as an immediate architectural code smell detector for violations of the Single Responsibility Principle (SRP):
// ❌ ANTI-PATTERN: Field Injection masks runaway class bloat
@Service
public class MegaBillingService {
@Autowired private AccountRepo accountRepo;
@Autowired private InvoiceRepo invoiceRepo;
@Autowired private PaymentGateway gateway;
@Autowired private AuditLogger logger;
@Autowired private EmailService emailService;
@Autowired private SmsService smsService;
@Autowired private CurrencyConverter converter;
@Autowired private FraudDetector fraudDetector;
@Autowired private MetricsClient metrics;
// 9 fields visually take only 9 neat lines, hiding the design disaster!
}
If this class is refactored to Constructor Injection:
public MegaBillingService(AccountRepo accountRepo, InvoiceRepo invoiceRepo,
PaymentGateway gateway, AuditLogger logger,
EmailService emailService, SmsService smsService,
CurrencyConverter converter, FraudDetector fraudDetector,
MetricsClient metrics) { ... }
A constructor demanding 9 parameters is an immediate red flag. The visual weight forces the developer to recognize that MegaBillingService is doing far too much and must be decomposed into dedicated orchestrators and cohesive domain services.
2. When is Setter Injection Justified?
Setter Injection is not deprecated, but its use case is strictly limited:
- Truly Optional Dependencies: When a service can function properly without the dependency by falling back to a sensible default:
@Service public class ReportExporter { private CompressionCodec codec = new DefaultGzipCodec(); // Default fallback @Autowired(required = false) public void setCompressionCodec(CompressionCodec codec) { this.codec = codec; } } - Configuration Property Binding: Spring Boot’s
@ConfigurationPropertiesclasses rely on standard getters and setters to bind values fromapplication.yamldynamically.
3. Spring’s Constructor Resolution Algorithm
When Spring creates a bean instance, it follows a deterministic constructor selection rule:
Bean Instantiation Constructor Selection:
├─ Exactly ONE constructor declared?
│ └─ YES ──> Automatically used for DI (No @Autowired needed since Spring 4.3)
└─ MULTIPLE constructors declared?
├─ Is one explicitly annotated with @Autowired?
│ └─ YES ──> Use the @Autowired constructor
└─ NO @Autowired on any constructor?
├─ Does a public no-arg constructor exist?
│ └─ YES ──> Invoke no-arg constructor (Dependencies wired via field/setter)
└─ NO no-arg constructor?
└─ CRASH ──> Throws BeanCreationException (No default constructor found)
🔴 Senior Level
1. CGLIB Proxy Creation & Constructor Execution Hazards
When a bean is marked with cross-cutting concerns like @Transactional, @Async, or @Cacheable, Spring creates a dynamic CGLIB subclass proxy that wraps the bean:
[ OrderService$$SpringCGLIB$$0 ] (Dynamic Proxy Subclass)
│ extends
▼
[ OrderService ] (Target Bean)
The Golden Rule: Constructors Must Only Assign Fields
A Spring Bean constructor must never perform heavy computation, database queries, or network I/O:
// ❌ DANGEROUS ANTI-PATTERN:
public OrderService(OrderRepository repo) {
this.repo = repo;
this.repo.preWarmDatabaseCache(); // DANGER: Unproxied call + duplicate execution!
}
Why This Breaks:
- AOP Proxies Are Not Yet Created: The dynamic AOP proxy wrapper is created during the Post-Initialization Phase (
BeanPostProcessor.postProcessAfterInitialization). During constructor execution, the object is a raw unproxied instance. Annotations like@Transactionalor@Asyncdo not work inside constructors. - Duplicate Invocation: CGLIB proxies invoke
super()when the proxy subclass is instantiated. If the superclass constructor performs expensive operations, they may execute more than once. - Remedy: Always place post-construction business logic inside a
@PostConstructmethod or anApplicationListener<ApplicationReadyEvent>.
2. Circular Dependencies and the Fallacy of @Lazy
Constructor Injection intentionally prevents circular dependencies ($A \to B \to A$) by throwing a fail-fast BeanCurrentlyInCreationException at startup.
While Spring allows breaking the cycle by annotating a constructor parameter with @Lazy:
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB; // Injects a dynamic CGLIB proxy placeholder
}
}
Why @Lazy is an Anti-Pattern in Production:
@Lazyinjects a synthetic CGLIB proxy that intercepts method calls and dynamically queries theApplicationContexton the first method invocation.- It conceals a serious architectural flaw (tight coupling and SRP violation).
- It introduces thread contention during lazy proxy initialization and can lead to subtle deadlock risks.
- Production Solution: Decouple the services by extracting shared logic into a third service
ServiceC, or replace the direct synchronous call cycle with an event-driven architecture using Spring’sApplicationEventPublisher.
3. Java Memory Model (JMM) §17.5 & Safe Publication
Constructor Injection provides rigorous concurrency guarantees under the Java Memory Model:
- When fields are declared
finaland set inside the constructor, the JMM guarantees that any thread obtaining a reference to that object will observe the fully constructedfinalfield references without data races or memory reordering hazards (Safe Publication). - Non-final fields (used in Field or Setter Injection) do not share this guarantee. If an object reference escapes or is passed across threads before field injection completes, other threads could theoretically observe stale
nullreferences.
4 Tricky Questions
1. Why did the Spring team drop the requirement for @Autowired on single constructors in Spring 4.3?
Answer: This change aligned with Spring’s core philosophy of being a non-invasive framework.
If a domain class has only a single constructor with arguments, the developer’s intent is unambiguous: the class cannot exist without those dependencies. Requiring @Autowired forced classes to import org.springframework.beans.factory.annotation.Autowired, polluting pure domain logic with framework-specific annotations. Dropping the requirement enables developers to write 100% framework-independent POJO classes that Spring can manage seamlessly.
2. Why does @Transactional fail to manage a transaction if invoked inside a bean constructor?
Answer:
Spring manages transactions using AOP dynamic proxies. The proxy intercepts incoming method calls and opens/commits database transactions via TransactionInterceptor.
In Spring’s Bean Lifecycle:
- The Constructor runs during the Instantiation Phase to create the raw target object in heap memory.
- The AOP Proxy is only created at the end of the Initialization Phase by
AnnotationAwareAspectJAutoProxyCreator(aBeanPostProcessor).
Inside the constructor, the bean is executing on raw this, prior to proxy creation. Because no proxy exists yet to intercept the invocation, @Transactional is completely bypassed. Any transactional initialization must be deferred to an @EventListener(ApplicationReadyEvent.class) or a transaction template.
3. What occurs if a class has two constructors: one with @Autowired and another with no arguments?
Answer:
Spring will strictly choose the constructor annotated with @Autowired.
Spring’s constructor resolution mechanism scans candidate constructors via AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(). Any constructor marked with @Autowired(required = true) takes absolute precedence over unannotated constructors, regardless of whether a public no-arg constructor is present.
If neither constructor had @Autowired, Spring would fall back to invoking the default no-arg constructor, leaving any parameterized constructors unused.
4. How does @Lazy on a constructor parameter break a circular dependency under the hood, and what are its risks?
Answer:
When Spring encounters @Lazy on a constructor argument:
- It does not resolve or instantiate the target dependency immediately.
- Instead, it generates a synthetic dynamic proxy (using CGLIB or JDK dynamic proxies) implementing the requested interface/class.
- This proxy is injected into the constructor, allowing the calling bean to finish its initialization.
- When the bean calls any method on that proxy at runtime, the proxy’s
TargetSourceintercepts the call, retrieves the real target singleton from theBeanFactory, and delegates the invocation.
Risks:
- Masks poor architectural design and cyclical coupling.
- May delay startup errors to runtime traffic (a broken dependency fails during a user request instead of failing fast at boot).
- Can cause race conditions and transaction context mismatches if the lazy bean attempts cross-thread interactions before being initialized.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“Constructor Injection is the industry and Spring team standard for all mandatory dependencies. It enforces immutability via
finalfields, guarantees non-null objects by eliminating half-initialized states, provides JMM safe publication across threads, and enables pure POJO unit testing without Spring or Mockito reflection. Furthermore, it acts as an architectural code smell detector—classes with more than 4–5 constructor parameters immediately signal SRP violations. Setter Injection is reserved solely for optional dependencies (@Autowired(required = false)), while Field Injection is an obsolete anti-pattern.”
Red Flags & Pitfalls (DO NOT Say)
- ❌ “Field injection is faster to write and has no downside in small projects.” (Lombok
@RequiredArgsConstructorprovides the exact same brevity with none of the architectural hazards). - ❌ “Using
@Lazyis the standard way to fix circular dependencies.” (It is an architectural band-aid; the correct fix is decomposing the classes or using events). - ❌ “I execute database queries in my constructor to initialize caches.” (Constructors run before AOP proxies are created; transactions and caching annotations will fail silently).
- ❌ “Constructor injection requires reflection at runtime for every method call.” (Constructor injection occurs once at instantiation time via standard JVM
newInstanceor bytecode).
Quick Comparison Summary
Injection Method Comparison:
┌───────────────────────┬──────────────┬───────────────┬────────────────────────┐
│ Feature │ Constructor │ Setter │ Field │
├───────────────────────┼──────────────┼───────────────┼────────────────────────┤
│ Immutability (final) │ ✅ YES │ ❌ NO │ ❌ NO │
│ NPE Safety │ ✅ 100% │ ⚠️ Risky │ ❌ Uninitialized │
│ POJO Unit Testing │ ✅ Trivial │ ⚠️ Moderate │ ❌ Reflection Required │
│ SRP Smell Visibility │ ✅ High │ ⚠️ Concealed │ ❌ Concealed │
│ Recommended Usage │ Mandatory │ Optional only │ Never (Anti-pattern) │
└───────────────────────┴──────────────┴───────────────┴────────────────────────┘