RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Spring Boot questions
MediumSpring Boot#147 min readJul 11, 2026

Why is Constructor Injection recommended?

testing
spring-boot
di
ioc
Practice modeTest yourself instead of reading straight through

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.

🧠 Memory Map
Memory map — visual summary of this topic

What Constructor Injection Means

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.

How It Works Under the Hood

  1. Spring finds a bean definition, such as a @Service, @Component, or a @Bean method.
  2. It inspects the constructor or factory method parameters to see what dependencies are needed.
  3. For each parameter, Spring looks up a matching bean by type, and if needed by @Qualifier or @Primary.
  4. Spring creates those dependencies first, then calls the constructor once with real objects.
  5. The object is now fully initialized, so Spring can apply post-processing, proxies, and lifecycle callbacks such as @PostConstruct.
  6. From that point on, the bean is used normally; injection is not repeated for every request.

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).

Why Spring Engineers Prefer It

  • Required dependencies are obvious. When you read the constructor, you know exactly what the class needs to function.
  • It supports immutability. Immutability means the object cannot be changed after creation; with constructor injection, fields can be final.
  • It fails fast. If Spring cannot find a required bean, the app refuses to start instead of letting a null leak into production.
  • It is test-friendly. You can instantiate the class with mocks in a plain unit test, without Spring context startup.
  • It avoids partially constructed objects. With field injection, the object is created first and dependencies arrive later; with constructor injection, there is no in-between state.

Constructor vs Field vs Setter Injection

StyleBest ForMain RiskInterview Takeaway
ConstructorRequired depsCircular refsPreferred default
FieldQuick demosHidden depsHarder to test
SetterOptional depsPartially ready beanUse sparingly

Practical Details Interviewers Probe

  • Single constructor rule: Since Spring 4.3, if a bean has only one constructor, Spring can inject it automatically; @Autowired is usually optional.
  • Multiple constructors: If there are several constructors, Spring needs guidance such as @Autowired on the intended one.
  • Performance: Injection happens at startup, not per request. The wiring cost is roughly linear in the number of dependencies, and for a normal service bean it is tiny compared with database or network calls.
  • Circular dependencies: Constructor injection exposes them clearly, which is usually good because a cycle often means the design needs to be split.

When Not To Force It

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.

Real-World Story

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.

Spring Boot
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:

  • Why does Spring Boot often not require @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.
  • How do you inject optional dependencies? Prefer a setter, ObjectProvider, or another lazy lookup when the dependency is truly optional. If the class cannot function without it, keep it in the constructor.
  • What happens with multiple beans of the same type? Spring needs help deciding which one to inject, usually with @Qualifier or @Primary. Constructor injection does not remove that rule; it just makes the dependency path clearer.
  • Why is constructor injection better for unit tests? You can create the class with plain Java and pass mocks or stubs directly. That means faster tests and no Spring context unless you specifically need integration testing.
  • What if there is a circular dependency? Constructor injection usually exposes it immediately, which is a sign the design should be refactored. Setter injection can sometimes hide a cycle, but hiding it is not the same as fixing it.
  • Is constructor injection always better than field injection? For required dependencies, yes, almost always. For optional or late-bound collaborators, another style can be appropriate, but field injection is still the least testable and least explicit choice.
  • Does constructor injection guarantee immutability? It enables immutability, but only if you also use final fields and avoid setters. The constructor is the mechanism; your class design completes the immutability story.
  • Is constructor injection faster at runtime? Not in any meaningful way for request handling, because the wiring happens once at startup. The real benefit is design quality, not micro-optimization.

Tricky / Gotcha Questions:

  • Can Spring inject a missing required dependency as null? Normally no; the app fails to start with a bean resolution error. A null is more likely when you manually instantiate the class or make the dependency optional by design.
  • Is @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.
  • Can constructor injection solve circular dependencies? No; it usually makes them visible, which is helpful. If you hit one, redesign the classes or use a different approach only as a last resort.

Common Mistakes:

  • Putting optional dependencies in the constructor: This makes the class harder to create and test. Correction: keep the constructor for must-have collaborators only.
  • Using field injection because it is shorter: The code may look smaller, but the dependency is hidden and the object can be partially constructed. Correction: prefer constructor injection for clearer design.
  • Adding @Autowired everywhere: It creates noise and does not add value on a single constructor. Correction: let Spring inject a lone constructor automatically.
  • Ignoring circular dependencies: Constructor injection exposes them, which is often the first clue that the design needs refactoring. Correction: split responsibilities instead of fighting the container.

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:

  • Constructor injection is the default choice for required dependencies.
  • It makes dependencies explicit and the object fully initialized.
  • Use final fields to reinforce immutability and clarity.
  • Spring Boot auto-wires a single constructor without extra annotations.
  • It is easier to unit test and it fails fast when beans are missing.
  • Use setters or ObjectProvider only for truly optional collaborators.

Practice Tasks:

  • Convert one field-injected service in your project to constructor injection.
  • Write a plain unit test that creates the service with mocks and no Spring context.
  • Temporarily remove one required bean and observe the startup failure message.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

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()); } } }