Interviewers love this question because it reveals whether you design for safety, not just for code that happens to run.
Question: Why is Constructor Injection recommended?
Answer: Constructor injection makes a class declare exactly what it needs, so required dependencies are supplied once when the object is created. That means the object cannot exist in a half-ready state, the fields can be final, and tests can create the class without starting the whole Spring container. In Spring Boot, if a required bean is missing, the application fails fast at startup instead of crashing later in production.
Interview-Ready Answer: I recommend constructor injection because it makes dependencies explicit and required, which gives me safer, more maintainable code. I can mark the fields final, avoid nulls and partially built objects, and write plain unit tests with mocks instead of booting Spring. A nice Spring detail is that with a single constructor, Spring Boot injects it automatically, so the code stays clean and the app fails fast if a dependency is missing.
In Spring Boot, IoC (Inversion of Control) means Spring creates and manages your objects. DI (Dependency Injection) means Spring passes the needed collaborators into your class instead of your class creating them itself. Constructor injection is the simplest DI style: the dependency is passed through the constructor, so the object is complete the moment it is born.
@Service, @Component, or a @Bean method.@Qualifier or @Primary.@PostConstruct.The key mental model is: constructor injection is a contract. If a dependency is required, it belongs in the constructor. If it is optional, use a different mechanism such as a setter or ObjectProvider (a Spring handle that asks for a bean only when needed).
final.| Style | Best For | Main Risk | Interview Takeaway |
|---|---|---|---|
| Constructor | Required deps | Circular refs | Preferred default |
| Field | Quick demos | Hidden deps | Harder to test |
| Setter | Optional deps | Partially ready bean | Use sparingly |
@Autowired is usually optional.@Autowired on the intended one.Use constructor injection for required dependencies. If a dependency is genuinely optional, dynamic, or expensive to create only sometimes, a setter, ObjectProvider, or a lazy lookup can make more sense. The rule is simple: required in constructor, optional outside constructor.
Memory model: think of constructor injection as handing a builder every tool before the house is assembled. If a hammer is missing, you stop at the blueprint stage instead of discovering the problem after the roof is on.
Imagine a checkout service in an e-commerce system that needs a payment gateway, a receipt formatter, and a clock. A team used field injection because it looked shorter, then later one service instance was created manually in a scheduled job and one dependency was forgotten. Everything compiled, but the field stayed null, and the first large promo sale started throwing NullPointerException during payment capture.
What the incident looked like: users clicked Pay and got 500 errors, support tickets spiked, and logs showed lines like Cannot invoke ... because paymentGateway is null. The carts were abandoned, some orders were left in a pending state, and the on-call engineer had to roll back traffic while the team traced the problem. If constructor injection had been used, the class could not have been created without its required collaborators, and the bug would have been caught at startup or in a simple unit test.
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.Instant;
import java.util.UUID;
@SpringBootApplication
public class ConstructorInjectionDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ConstructorInjectionDemoApplication.class, args);
}
@Bean
Clock clock() {
return Clock.systemUTC();
}
@Bean
PaymentGateway paymentGateway() {
return amount -> "PAY-" + UUID.randomUUID();
}
@Bean
ReceiptFormatter receiptFormatter() {
return new ReceiptFormatter();
}
@Bean
CheckoutService checkoutService(PaymentGateway paymentGateway, ReceiptFormatter receiptFormatter, Clock clock) {
// Spring resolves these dependencies first, then calls the constructor.
// If one of them is missing, the app fails at startup instead of hiding a null reference.
return new CheckoutService(paymentGateway, receiptFormatter, clock);
}
@Bean
CommandLineRunner demo(CheckoutService checkoutService) {
return args -> {
System.out.println(checkoutService.checkout("order-123", new BigDecimal("19.99")));
try {
System.out.println(checkoutService.checkout("order-124", BigDecimal.ZERO));
} catch (IllegalArgumentException ex) {
System.out.println("Expected failure: " + ex.getMessage());
}
};
}
interface PaymentGateway {
String charge(BigDecimal amount);
}
static class ReceiptFormatter {
String format(String orderId, String paymentId, Instant paidAt) {
return "receipt=" + orderId + ", payment=" + paymentId + ", at=" + paidAt;
}
}
static class CheckoutService {
private final PaymentGateway paymentGateway;
private final ReceiptFormatter receiptFormatter;
private final Clock clock;
// Constructor injection makes required dependencies explicit and non-null.
CheckoutService(PaymentGateway paymentGateway, ReceiptFormatter receiptFormatter, Clock clock) {
this.paymentGateway = paymentGateway;
this.receiptFormatter = receiptFormatter;
this.clock = clock;
}
String checkout(String orderId, BigDecimal amount) {
if (amount == null || amount.signum() <= 0) {
throw new IllegalArgumentException("Amount must be greater than zero");
}
String paymentId = paymentGateway.charge(amount);
return receiptFormatter.format(orderId, paymentId, clock.instant());
}
}
}
Follow-up & Tricky Questions:
@Autowired on a constructor? Since Spring 4.3, a class with a single constructor is treated as injectable automatically, so the annotation is usually redundant. That keeps the code cleaner without changing behavior.ObjectProvider, or another lazy lookup when the dependency is truly optional. If the class cannot function without it, keep it in the constructor.@Qualifier or @Primary. Constructor injection does not remove that rule; it just makes the dependency path clearer.final fields and avoid setters. The constructor is the mechanism; your class design completes the immutability story.Tricky / Gotcha Questions:
@Autowired required on the only constructor? No, not in modern Spring if there is only one constructor. Many candidates still add it out of habit, but it is unnecessary.Common Mistakes:
@Autowired everywhere: It creates noise and does not add value on a single constructor. Correction: let Spring inject a lone constructor automatically.Memory Hook: Think of constructor injection like check-in at a hotel: you hand over every key before you enter the room. No key, no room; no dependency, no object.
Cheat Sheet:
final fields to reinforce immutability and clarity.ObjectProvider only for truly optional collaborators.Practice Tasks: