RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Spring Boot questions
TrickySpring Boot#137 min readJul 11, 2026

Constructor vs Setter vs Field Injection.

practice
learning
Practice modeTest yourself instead of reading straight through

Why interviewers love this one: it looks simple, but your answer shows whether you understand object design, testability, and how Spring actually wires beans.

Question: Constructor vs Setter vs Field Injection.

Answer: These are three ways Spring can give a bean its dependencies. Constructor injection passes required objects when the bean is created, setter injection assigns them later through methods, and field injection writes directly into variables with reflection (reflection means Spring sets the field without calling your code). In Spring Boot, constructor injection is usually the best default, setter injection is good for optional dependencies, and field injection is the least preferred because it hides what the class needs.

Interview-Ready Answer: I prefer constructor injection for required dependencies because it makes the class immutable, easier to test, and impossible to create in a half-broken state. I use setter injection when a dependency is optional or can change later. I avoid field injection in production code because the dependency is hidden, it is harder to unit test, and it relies on reflection. In Spring Boot, my rule is: constructor first, setter second, field only in quick demos or legacy code.

🧠 Memory Map
Memory map — visual summary of this topic

What each style really means

Constructor injection gives the dependency at object creation time. Setter injection gives it after the object exists. Field injection writes directly into a private field using reflection, which means Spring reaches into the object without calling your method.

StyleBest forStrengthTrade-off
ConstructorRequired depsImmutable, testableMore explicit
SetterOptional depsFlexible wiringCan be null
FieldQuick demosShortest syntaxHidden dependency

How Spring wires them under the hood

  1. Spring finds the bean definition from component scanning or a @Bean method.
  2. For constructor injection, Spring resolves all required arguments first and calls the constructor. With a single constructor, Spring Boot can use it automatically; you do not always need @Autowired.
  3. For setter injection, Spring creates the object first, then calls the setter methods to fill in dependencies.
  4. For field injection, Spring creates the object and then uses reflection to write directly into the field, even if it is private.
  5. After that, Spring runs bean post-processors and lifecycle hooks such as @PostConstruct, so the bean becomes ready for use.

When to use each one

  • Constructor injection: choose this for required collaborators like repositories, services, clients, and calculators. It makes invalid objects impossible.
  • Setter injection: choose this for optional collaborators, late binding, or a dependency you may swap in tests. A setter can also be combined with @Autowired(required = false) or Optional<T> when the bean may not exist.
  • Field injection: avoid it in normal application code. It is concise, but it hides the contract of the class and makes plain Java tests awkward.

Important gotchas and real trade-offs

  • Constructor injection fails fast at startup if a required bean is missing. That is good: you find the problem before traffic hits production.
  • Setter and field injection can leave a bean in a partially initialized state if the dependency is missing or the object is created outside Spring.
  • Field injection cannot use final fields, so you lose a clean immutability pattern.
  • Circular dependencies are a warning sign. Constructor injection exposes them early, while setter or field injection can hide them until runtime. In Spring Boot 2.6+, circular references are disabled by default, so the real fix is usually to redesign the code.
  • The performance difference is tiny. Bean wiring happens once at startup, and the extra reflection work for field or setter injection is usually microseconds per bean. In interviews, correctness and maintainability matter far more than this small startup cost.

Rule of thumb: required dependency = constructor, optional dependency = setter, hidden dependency = usually a smell.

Real-world story

Imagine an e-commerce checkout service. The TaxCalculator and PaymentGateway are required, so constructor injection keeps the service honest: it cannot even start if those pieces are missing. A FraudRulesEngine might be optional in some environments, so setter injection lets the app run with a safe default when that engine is not configured. One team used field injection in a refund batch worker, then wrote a plain unit test with new instead of Spring. The field stayed null, the job threw NullPointerException, refunds stalled, and logs showed messages like Cannot invoke ... because ... is null. Users saw delayed refunds and support tickets spiked.

Spring Boot
package com.example.injectiondemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@SpringBootApplication
public class InjectionDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(InjectionDemoApplication.class, args);
    }

    @Bean
    CommandLineRunner demo(CheckoutService checkoutService,
                           OptionalBannerService optionalBannerService,
                           FieldInjectedDiagnostics diagnostics) {
        return args -> {
            double subtotal = 100.0;

            System.out.println("Constructor injection: total = " + checkoutService.finalTotal(subtotal));
            System.out.println("Setter injection: " + optionalBannerService.bannerFor("Spring Boot"));
            System.out.println("Field injection: " + diagnostics.beanSummary());

            // Edge case: plain new() bypasses Spring, so field-injected dependencies stay null.
            try {
                FieldInjectedDiagnostics broken = new FieldInjectedDiagnostics();
                System.out.println(broken.beanSummary());
            } catch (NullPointerException ex) {
                System.out.println("Manual construction failed as expected: " + ex.getClass().getSimpleName());
            }
        };
    }
}

@Component
class TaxCalculator {
    double taxFor(double subtotal) {
        return subtotal * 0.10;
    }
}

@Service
class CheckoutService {
    private final TaxCalculator taxCalculator;

    // Constructor injection makes required dependencies explicit and the object valid immediately.
    CheckoutService(TaxCalculator taxCalculator) {
        this.taxCalculator = taxCalculator;
    }

    double finalTotal(double subtotal) {
        return subtotal + taxCalculator.taxFor(subtotal);
    }
}

@Component
class BannerFormatter {
    String format(String text) {
        return "[formatted] " + text;
    }
}

@Service
class OptionalBannerService {
    private BannerFormatter formatter;

    // Setter injection is useful when the collaborator is optional or swappable.
    @Autowired(required = false)
    public void setFormatter(BannerFormatter formatter) {
        this.formatter = formatter;
    }

    String bannerFor(String text) {
        // If Spring does not supply the bean, we still have a safe fallback.
        return formatter == null ? text : formatter.format(text);
    }
}

@Component
class FieldInjectedDiagnostics {
    // Hidden dependency: the class looks empty, but Spring must inject this field.
    @Autowired
    private ApplicationContext applicationContext;

    String beanSummary() {
        return "Spring manages " + applicationContext.getBeanDefinitionCount() + " bean definitions";
    }
}

Follow-up & Tricky Questions:

  • When should I use constructor injection? Use it for anything required for the object to work. If a bean cannot do its job without a dependency, constructor injection is the cleanest and safest choice.
  • When is setter injection a good idea? Use it for optional collaborators, late configuration, or values that may change in tests. It is also useful when you want a sensible fallback if the dependency is missing.
  • Why is field injection considered bad practice? The dependency is hidden, the class is harder to test with plain Java, and you cannot make the field final. It also encourages weak design because the object can exist in a broken state.
  • How does Spring choose which constructor to use? If there is only one constructor, Spring Boot uses it automatically. If there are multiple constructors, you usually mark the one you want with @Autowired.
  • What about circular dependencies? Constructor injection exposes them early, which is helpful. Setter and field injection may delay the pain, but the real solution is to remove the cycle or refactor the design.
  • Is field injection ever acceptable? In small demos, sample code, or very old codebases, yes. In production code, most teams still prefer constructor injection because the trade-offs are better.
  • Can constructor injection handle optional dependencies? Yes, but use Optional<T>, @Nullable, or a default object carefully. If the dependency is truly optional, many teams still prefer setter injection because it reads more naturally.
  • Does setter injection guarantee the object is ready? Not by itself. The object can exist before the setter is called, so you must code defensively or make the dependency optional by design.

Tricky / gotcha questions:

  • Can I make a field-injected dependency final? No. Spring sets field-injected values after construction, and final fields must be assigned during construction.
  • Does setter injection solve every circular dependency? No. It may hide the problem, but modern Spring Boot often fails fast instead of silently fixing it. Refactoring is the correct answer.
  • Is constructor injection slower because it passes more arguments? Not in a meaningful way. The bean is created once, and the small startup cost is worth the stronger design.

Common Mistakes:

  • Using field injection by default. Correction: make constructor injection your default and reach for field injection only in demos or legacy code.
  • Forgetting that setter injection can leave null. Correction: check for optional dependencies explicitly or provide a safe fallback.
  • Thinking constructor injection is only for services. Correction: it is also great for controllers, repositories, helpers, and almost any class with required collaborators.
  • Ignoring circular dependencies. Correction: do not depend on injection style to save a bad design; break the cycle instead.

Memory Hook: Build it, then add it, then sneak it in. Constructor injection means the parts are in the box before you close it; setter injection means you add a part after assembly; field injection means you slip the part through a hidden panel.

Cheat Sheet:

  • Constructor = best default for required dependencies.
  • Setter = good for optional or replaceable dependencies.
  • Field = shortest syntax, weakest design.
  • Constructor injection supports immutability and easier tests.
  • Setter and field injection can leave null if wiring is incomplete.
  • Spring Boot 2.6+ disables circular references by default, so design matters.

Practice Tasks:

  • Rewrite one of your own Spring services to use constructor injection only.
  • Take an optional collaborator and convert it to setter injection with a safe fallback.
  • Write a tiny unit test that manually creates a field-injected class and observe why it fails without Spring.
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

package com.example.injectiondemo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @SpringBootApplication public class InjectionDemoApplication { public static void main(String[] args) { SpringApplication.run(InjectionDemoApplication.class, args); } @Bean CommandLineRunner demo(CheckoutService checkoutService, OptionalBannerService optionalBannerService, FieldInjectedDiagnostics diagnostics) { return args -> { double subtotal = 100.0; System.out.println("Constructor injection: total = " + checkoutService.finalTotal(subtotal)); System.out.println("Setter injection: " + optionalBannerService.bannerFor("Spring Boot")); System.out.println("Field injection: " + diagnostics.beanSummary()); // Edge case: plain new() bypasses Spring, so field-injected dependencies stay null. try { FieldInjectedDiagnostics broken = new FieldInjectedDiagnostics(); System.out.println(broken.beanSummary()); } catch (NullPointerException ex) { System.out.println("Manual construction failed as expected: " + ex.getClass().getSimpleName()); } }; } } @Component class TaxCalculator { double taxFor(double subtotal) { return subtotal * 0.10; } } @Service class CheckoutService { private final TaxCalculator taxCalculator; // Constructor injection makes required dependencies explicit and the object valid immediately. CheckoutService(TaxCalculator taxCalculator) { this.taxCalculator = taxCalculator; } double finalTotal(double subtotal) { return subtotal + taxCalculator.taxFor(subtotal); } } @Component class BannerFormatter { String format(String text) { return "[formatted] " + text; } } @Service class OptionalBannerService { private BannerFormatter formatter; // Setter injection is useful when the collaborator is optional or swappable. @Autowired(required = false) public void setFormatter(BannerFormatter formatter) { this.formatter = formatter; } String bannerFor(String text) { // If Spring does not supply the bean, we still have a safe fallback. return formatter == null ? text : formatter.format(text); } } @Component class FieldInjectedDiagnostics { // Hidden dependency: the class looks empty, but Spring must inject this field. @Autowired private ApplicationContext applicationContext; String beanSummary() { return "Spring manages " + applicationContext.getBeanDefinitionCount() + " bean definitions"; } }