Setter & Field Injection
Setter & Field Injection
Spring supports three styles of dependency injection: constructor, setter, and field. The previous lesson established constructor injection as the recommended default. This lesson covers the remaining two — not because you should use them freely, but because you will encounter them in real codebases and need to understand exactly what makes constructor injection the safer choice. Knowing the trade-offs is what lets you make deliberate decisions instead of following cargo-cult conventions.
Setter Injection
With setter injection you annotate a public (or package-private) setter method with @Autowired. Spring instantiates the bean first and then calls each annotated setter to inject the dependency.
The key difference from constructor injection is timing: when generate() is called, notificationSender could theoretically still be null if Spring's wiring has not yet completed — for example, inside the constructor body itself, or during early lifecycle callbacks that fire before all setters have been called.
required = false attribute on @Autowired signals this intent. If a dependency is truly mandatory, constructor injection enforces that contract at startup; setter injection does not.
Field Injection
Field injection annotates the field directly with @Autowired. Spring uses reflection to set the value after construction, bypassing any access modifier.
Field injection is the most concise style and was popular in early Spring projects because it required the least boilerplate. IntelliJ IDEA and Spring's own documentation now flag it with a warning: "Field injection is not recommended."
Why Constructor Injection Is Preferred — the Full Argument
The preference for constructor injection is not aesthetic; it maps directly to concrete engineering problems:
-
Immutability and final fields. A constructor-injected field can be declared
final. That makes the dependency guaranteed to be set and impossible to replace at runtime — preventing an entire class of bugs where a collaborator is accidentally re-assigned or never set. -
Null-safety guaranteed at startup. If a required bean is missing, Spring throws
NoSuchBeanDefinitionExceptionduring application context startup, before any request is served. With field or setter injection the missing dependency only surfaces as aNullPointerExceptioninside a running request, often in production. -
Testability without a Spring context. With constructor injection you can instantiate and test a class with plain Java:
// No Spring context, no Mockito magic — pure Java OrderService service = new OrderService( new FakePaymentGateway(), new InMemoryInventoryRepository() );With field injection, the fields are private and inaccessible without either Spring or a reflection-based test runner. You must use
@ExtendWith(SpringExtension.class)orReflectionTestUtils.setField(), both of which add complexity and slow down the test suite. -
Circular dependency detection. Spring will throw a
BeanCurrentlyInCreationExceptionat startup if two beans have a circular constructor dependency (A needs B, B needs A). That is a good thing — it forces you to redesign. Field or setter injection silently allows circular graphs to exist and can hide architectural problems for months. -
Violation of the Single Responsibility Principle becomes obvious. When a class needs eight injected dependencies, eight constructor parameters are painful to read and write. That pain is a signal to split the class. Eight
@Autowiredfields, by contrast, are easy to ignore and accumulate invisibly.
@Autowired on private fields cannot be used outside Spring at all — not in unit tests, not in a batch script, not in a library consumed by another project. You have permanently coupled the class to the IoC container. Constructor injection keeps the class a plain Java object; Spring is just one way to wire it.
Mixing Styles in a Real Codebase
A realistic rule of thumb used in professional teams:
- Mandatory dependencies: always constructor injection, fields declared
final. - Optional dependencies (can be absent, class still functions): setter injection with
@Autowired(required = false). - Field injection: reserved for short-lived test classes (e.g.,
@SpringBootTestintegration tests where you just want to pull a bean out of the context without writing a constructor). Never in production beans.
@RequiredArgsConstructor (from Project Lombok) and all final fields get a constructor generated at compile time with no boilerplate. Since Spring 4.3, if there is exactly one constructor, @Autowired on it is optional — Spring infers it automatically.
Summary
Setter injection is useful for optional dependencies where a sensible default behaviour exists in the absence of the collaborator. Field injection is the most concise but the most problematic: it destroys testability without Spring, hides missing dependencies until runtime, prevents final fields, and couples the class permanently to the container. Constructor injection avoids all of these problems, makes the dependency contract explicit in the API, and should be the default for every mandatory collaborator. The next lesson examines the annotations — @Autowired, @Qualifier, and @Primary — that drive all three styles.