Constructor Injection
Constructor Injection
Of the three styles of dependency injection Spring supports — constructor, setter, and field — constructor injection is the one the Spring team recommends as the default. It produces objects whose dependencies are satisfied the moment the object exists, which makes the code easier to test, easier to reason about, and harder to misuse. This lesson explores why that is true and what the style looks like in real production code.
Why Constructor Injection is Preferred
The core advantage is immutability. When a dependency arrives through the constructor you can declare the field final. A final field cannot be reassigned after the constructor runs — by the JVM's own memory model guarantee. That means:
- No class can swap the dependency out from under you after construction.
- The object is inherently thread-safe with respect to its injected fields.
- The compiler enforces that every required dependency is supplied — a missing argument is a compile error, not a
NullPointerExceptionat runtime.
There is a second, practical benefit: because the dependencies are constructor parameters, you can instantiate the class in a plain unit test with new, passing mock objects as arguments. No Spring context, no reflection magic, no @SpringBootTest required for simple unit tests.
A Realistic Example
Imagine an e-commerce service that sends order confirmation emails. It depends on two collaborators: a OrderRepository that reads orders from the database, and a NotificationService that dispatches the email. Here is how you wire them with constructor injection:
Both fields are final. There is no default constructor. If Spring cannot find a bean of type OrderRepository or NotificationService in the application context, it fails at startup with an informative error — not silently at runtime when confirmOrder is first called.
@Autowired. This is the common case and keeps the code annotation-free. Add @Autowired only when you have multiple constructors and need to tell Spring which one to use.
The @Autowired Annotation on Constructors
Before Spring 4.3, every constructor meant for injection had to be annotated. You still see this in older codebases, and you need it when a class has more than one constructor:
@Autowired on a constructor tells Spring: "use this one when assembling the bean." The annotation's required attribute defaults to true, so an unsatisfied parameter throws an exception at startup. Setting required = false is rarely correct for constructor injection — prefer optional setter injection for genuinely optional collaborators.
Immutability in Practice — the final Keyword
Declaring fields final is not just a stylistic choice; it activates the JVM's happens-before guarantee. Any thread that sees the fully constructed object also sees the values assigned in the constructor. This matters in Spring because beans created in one thread (the context-startup thread) are later used by many request threads.
final. If you find yourself unable to do so — perhaps because the field must be reassigned later — ask whether you actually need setter injection or whether the design should be reconsidered. Most real services have stable collaborators that never change after construction.
Constructor Injection and Unit Testing
One of the greatest benefits shows up in tests. Compare the two approaches:
You instantiate the class directly with test doubles — no @MockBean, no application-context startup overhead, millisecond-fast tests. Field injection removes this option entirely; Mockito's @InjectMocks works by reflection, is fragile, and still has to discover and inject each field individually.
When Constructor Injection Gets Unwieldy
If a constructor grows beyond four or five parameters that is a code smell, not a reason to switch to field injection. It usually means the class has too many responsibilities. Refactor by extracting a cohesive group of dependencies into a new service, or by identifying which dependencies belong to a new layer of abstraction.
@Autowired on private fields just to avoid a long constructor makes the dependency list invisible — it does not make it shorter. Tools like IntelliJ can warn you when a Spring bean has more than a configured number of injected fields; enable that inspection.
Lombok @RequiredArgsConstructor
Many teams use Lombok to eliminate the boilerplate. The annotation @RequiredArgsConstructor generates a public constructor for every final field (and every non-null field without a default) automatically at compile time:
The generated constructor is identical to what you would write by hand. Spring sees it and uses it. The class is still fully testable with new OrderConfirmationService(repo, notification) in tests. This is the pattern you will encounter most often in modern Spring projects.
Summary
Constructor injection is the preferred DI style in Spring because it enforces required dependencies at startup, enables final fields for immutability and thread safety, and produces classes that are straightforward to unit-test without a Spring context. A single constructor is detected and used automatically; @Autowired is needed only when multiple constructors exist. If a constructor grows long, treat it as a design signal rather than a reason to reach for field injection. In the next lesson you will see setter and field injection — styles that have legitimate but narrower use cases.