Question 3 · Section 5

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

Language versions: English Russian Ukrainian

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:

  1. Can make field final
  2. Application will crash immediately if dependency is missing
  3. 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

  1. Too many dependencies (10+) - a sign the class does too much (SRP violation). Break into smaller classes.
  2. Circular dependencies - Spring cannot create beans. Use setter injection or @Lazy.
  3. 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

  1. Constructor - always by default
  2. @RequiredArgsConstructor - Lombok for convenience
  3. @Lazy - not only for circular dependencies. Also useful for lazy loading of heavy beans (saves memory and startup time).
  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. 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
  • @Lazy resolves 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]]