Spring Boot
Spring Setter Injection: Optional Dependencies, @Autowired(required = false) and When to Use It
Learn Spring setter injection: how it works, when it beats constructor injection (optional dependencies), alternatives such as Optional and ObjectProvider, circular dependencies in Spring Boot 2.6+, testing, and common errors.
What Is Setter Injection?
With setter injection, Spring first creates the bean, then calls each `@Autowired` setter method to supply a dependency. Between construction and the last setter call, the object is only partly initialised.
The Spring reference documentation recommends:
- **Constructor injection** for mandatory dependencies.
- **Setter injection** mainly for optional dependencies that have a reasonable default inside the class.
An optional dependency is one where the class still works when it is absent: a service that publishes metrics only when a metrics bean exists, or a notifier that adds SMS only when an SMS provider is configured.
Examples target Spring Framework 6 and Spring Boot 3.x.
Setter vs Constructor vs Field Injection at a Glance
| | Constructor | Setter | Field |
|---|---|---|---|
| Best for | Mandatory dependencies | Optional dependencies | Avoid in application code |
| Fields can be `final` | Yes | No | No |
| Object fully built after construction | Yes | No | No |
| Easy to unit test without Spring | Yes | Yes | No (needs reflection) |
| Can express "optional" | Via `ObjectProvider` or `Optional` | Yes | Yes |
| Hides too many dependencies | No, a long constructor is a visible warning | Partly | Yes |
Since Spring 4.3, a class with **one constructor** needs no `@Autowired` on it. Setter methods still need the annotation.
When Do Setters Run? The Bean Lifecycle
Setter injection happens **after the constructor and before `@PostConstruct`**. Two consequences:
- Dependencies injected by setter are **`null` inside the constructor**. Don't use them there.
- They are available in `@PostConstruct` and `InitializingBean.afterPropertiesSet()`, so put start-up logic that needs them there.
The setter method doesn't need to be named `setX`. Spring calls any method annotated with `@Autowired`. It can also have several parameters, but then it is only called if **all** of them can be resolved.
Java
@Component
public class LifecycleDemo {
private Dependency dependency;
public LifecycleDemo() {
System.out.println("1. constructor, dependency = " + dependency); // null
}
@Autowired
public void setDependency(Dependency dependency) {
this.dependency = dependency;
System.out.println("2. setter called");
}
@PostConstruct
public void init() {
System.out.println("3. @PostConstruct, dependency = " + dependency); // injected
}
}Basic Setter Injection
`@Autowired` on a setter is **required by default**: if no matching bean exists, startup fails with `NoSuchBeanDefinitionException`. Add `required = false` to make the dependency optional. Spring then simply doesn't call the setter when no bean is found.
A required dependency belongs in the constructor, as the second class below shows. Injecting a mandatory dependency by setter leaves the object unusable if the setter is never called, and the field can't be `final`.
Java
// Works, but a constructor is better for a mandatory dependency
@Service
public class UserService {
private UserRepository userRepository; // cannot be final
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
// Preferred: required via constructor, optional via setter
@Service
public class NotificationService {
private final EmailService emailService; // required, final
private SmsService smsService; // optional, may stay null
public NotificationService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired(required = false)
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notify(String userId, String message) {
emailService.send(userId, message);
if (smsService != null) {
smsService.send(userId, message);
}
}
}Four Ways to Express an Optional Dependency
`@Autowired(required = false)` is one of four options. Pick one per dependency.
| Option | How it behaves when the bean is missing | Notes |
|---|---|---|
| `@Autowired(required = false)` on a setter | Setter is **not called**; the field keeps its default | Lets you set a default value on the field |
| `Optional<T>` parameter | Setter called with `Optional.empty()` | Explicit in the signature |
| `@Nullable` parameter | Setter **is called with `null`** | Different from `required = false` |
| `ObjectProvider<T>` parameter | Setter or constructor gets a lazy handle | Works with **constructor** injection, so fields can stay `final` |
`ObjectProvider` also handles "zero or many" and lets you supply a fallback with `getIfAvailable(...)`.
`required = false` does **not** suppress ambiguity. If two beans of the type exist and neither is `@Primary`, Spring can still throw `NoUniqueBeanDefinitionException`. Resolve that with `@Primary` or `@Qualifier`.
Java
@Service
public class OrderNotifier {
private SmsService sms = new NoOpSmsService(); // default when no bean exists
// Option 1: required = false (setter skipped when absent, default stays)
@Autowired(required = false)
public void setSms(SmsService sms) {
this.sms = sms;
}
// Option 2: Optional<T>
@Autowired
public void setSmsOptional(Optional<SmsService> sms) {
this.sms = sms.orElse(new NoOpSmsService());
}
// Option 3: @Nullable (setter IS called, with null)
@Autowired
public void setSmsNullable(@Nullable SmsService sms) {
if (sms != null) {
this.sms = sms;
}
}
}
// Option 4: ObjectProvider in a constructor, so the field can be final
@Service
public class OrderNotifierV2 {
private final SmsService sms;
public OrderNotifierV2(ObjectProvider<SmsService> smsProvider) {
this.sms = smsProvider.getIfAvailable(NoOpSmsService::new);
}
}
// Use only ONE option per dependency. They are shown together for comparison.Make the Optional Bean Actually Optional
`required = false` only tolerates a missing bean. The bean has to be created conditionally for the optional behaviour to switch on and off. `@ConditionalOnProperty` on the bean definition does that, so a property decides whether the dependency exists.
Prefer a **default no-op implementation** (the null-object pattern) over `if (x != null)` checks scattered through the code. The service calls it unconditionally, and the no-op does nothing.
Java
@Configuration
public class MetricsConfig {
@Bean
@ConditionalOnProperty(name = "metrics.enabled", havingValue = "true")
public MetricsCollector metricsCollector(MeterRegistry registry) {
return new MicrometerMetricsCollector(registry);
}
}
// application-prod.yml
// metrics.enabled: true -> MetricsCollector bean exists -> setter called
// application-dev.yml
// metrics.enabled: false -> no bean -> setter skipped -> default no-op stays
@Service
public class AuditableOrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private MetricsCollector metrics = MetricsCollector.NOOP; // null-object default
public AuditableOrderService(OrderRepository orderRepository, PaymentService paymentService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
@Autowired(required = false)
public void setMetrics(MetricsCollector metrics) {
this.metrics = metrics;
}
public Order createOrder(CreateOrderRequest request) {
PaymentResult payment = paymentService.charge(request.customerId(), request.totalAmount());
Order order = orderRepository.save(new Order(request, payment));
metrics.increment("orders.created"); // no null check needed
return order;
}
}Combining Constructor and Setter Injection
The usual pattern is **constructor injection for what the class needs, setter injection for what it can do without**. The class's API then documents which is which: anything in the constructor is mandatory.
Keep the number of optional setters small. A class with many optional collaborators is often doing too much, and splitting it up is better than adding more `if (x != null)` branches.
Java
@Service
public class ReportingService {
// REQUIRED: constructor
private final ReportRepository reportRepository;
private final UserRepository userRepository;
private final PdfGenerator pdfGenerator;
// OPTIONAL: setters, each with a safe default
private ReportCache cache = ReportCache.NOOP;
private ReportStorage storage = ReportStorage.NOOP;
public ReportingService(ReportRepository reportRepository,
UserRepository userRepository,
PdfGenerator pdfGenerator) {
this.reportRepository = reportRepository;
this.userRepository = userRepository;
this.pdfGenerator = pdfGenerator;
}
@Autowired(required = false)
public void setCache(ReportCache cache) { this.cache = cache; }
@Autowired(required = false)
public void setStorage(ReportStorage storage) { this.storage = storage; }
public Report generateReport(Long userId, ReportType type) {
return cache.get(userId, type).orElseGet(() -> {
User user = userRepository.findById(userId).orElseThrow();
byte[] pdf = pdfGenerator.generate(user, type);
Report report = reportRepository.save(new Report(userId, type, pdf));
storage.store(report, pdf);
cache.put(userId, type, report);
return report;
});
}
}Injecting a Collection by Setter
A setter can take a `List` or `Set` to receive **every bean of a type**. This is a natural fit for plug-in style designs. Order the list with `@Order` or `Ordered` on the beans.
With the default `required = true`, Spring fails if **no** bean of that type exists. With `required = false` the setter is skipped and the field keeps its default (an empty list below).
Java
@Service
public class NotificationDispatcher {
private List<NotificationChannel> channels = List.of(); // default: none
@Autowired(required = false)
public void setChannels(List<NotificationChannel> channels) {
this.channels = channels;
}
public void dispatch(String userId, String message) {
channels.forEach(channel -> channel.send(userId, message));
}
}
@Component @Order(1)
class EmailChannel implements NotificationChannel { /* ... */ }
@Component @Order(2)
class SmsChannel implements NotificationChannel { /* ... */ }Setter Injection and Circular Dependencies
Historically, Spring could resolve a cycle between singletons by exposing a half-built bean early and wiring the rest by setter. **Spring Boot 2.6 and later turn that off by default**, and the application fails at startup. This applies to setter and field cycles, not only to constructors.
```
The dependencies of some of the beans in the application context form a cycle:
serviceA
serviceB
```
- **Constructor cycle:** always fails (`UnsatisfiedDependencyException` caused by `BeanCurrentlyInCreationException`).
- **Setter or field cycle:** works only if `spring.main.allow-circular-references=true`. Don't rely on it. The cycle is a design smell.
Better fixes:
1. **Extract** the shared logic into a third service both can depend on.
2. **Use events** so one side notifies the other without a direct reference.
3. **Move one dependency to a method parameter** instead of a field.
4. As a last resort, add **`@Lazy`** on one injection point, which injects a lazy proxy.
Java
// Fails at startup on Spring Boot 2.6+ unless circular references are allowed
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; }
}
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; }
}
# Re-enables the old behaviour (not recommended)
spring.main.allow-circular-references=true
// Last-resort alternative: break the cycle with a lazy proxy
@Autowired
public void setServiceA(@Lazy ServiceA serviceA) { this.serviceA = serviceA; }Pitfalls of Setter Injection
- **Partial initialisation.** Until every setter has run, the object isn't complete. Never call methods that need a setter-injected dependency from the constructor.
- **Mutable state.** The fields can't be `final`, and a public setter lets any code swap a dependency at runtime. Keep setters for injection only, and don't call them from production code after start-up.
- **Static methods are ignored.** Spring doesn't inject into `static` setters.
- **Only Spring-managed instances are injected.** An object you create with `new` never gets its setters called.
- **`null` checks everywhere.** Use a null-object default or `ObjectProvider` to avoid them.
- **No-arg constructor needed.** If the class also has a constructor with parameters, Spring uses that one, and the setters then run afterwards.
- **`@Required` is gone.** It was deprecated in Spring 5.1 and removed in 6.0. Use constructor injection to make a dependency mandatory.
Testing Classes With Setter-Injected Dependencies
Both required and optional dependencies are easy to test without Spring: build the object with its constructor, and call the setter only in the test that needs the optional collaborator.
For a Spring integration test, replace a bean with `@MockitoBean` (Spring Framework 6.2 / Boot 3.4+) or `@MockBean` on older versions. Don't change a shared singleton by reflection, because the change leaks into later tests. To test the "bean absent" path, disable the conditional bean with a property.
Java
@ExtendWith(MockitoExtension.class)
class SearchServiceTest {
@Mock
private ProductRepository productRepository;
private SearchService searchService;
@BeforeEach
void setUp() {
searchService = new SearchService(productRepository); // no search client
}
@Test
void search_withoutSearchClient_queriesDatabase() {
when(productRepository.findByNameContaining("laptop"))
.thenReturn(List.of(new Product(1L, "Gaming Laptop")));
assertThat(searchService.search("laptop")).hasSize(1);
verify(productRepository).findByNameContaining("laptop");
}
@Test
void search_withSearchClient_usesIndex() {
SearchIndexClient client = mock(SearchIndexClient.class);
searchService.setSearchClient(client); // optional dependency via setter
when(client.search("laptop")).thenReturn(List.of(new Product(2L, "Laptop Pro")));
assertThat(searchService.search("laptop")).hasSize(1);
verifyNoInteractions(productRepository);
}
}
// Integration test: replace a bean, or switch the optional bean off
@SpringBootTest(properties = "metrics.enabled=false")
class OrderServiceWithoutMetricsTest {
@MockitoBean // @MockBean on Boot < 3.4
private PaymentService paymentService;
@Autowired
private AuditableOrderService orderService;
// runs with the no-op metrics default
}Setter Injection in XML Configuration
In XML configuration, `<property name="..." ref="..."/>` **is** setter injection, and `<constructor-arg>` is constructor injection. Legacy Spring applications use this heavily, which is why setters were the dominant style in older code. The Java-config equivalent is calling a setter inside a `@Bean` method.
XML
<bean id="emailService" class="com.example.service.SmtpEmailService">
<property name="host" value="smtp.example.com"/>
<property name="port" value="587"/>
</bean>
@Bean
public SmtpEmailService emailService() {
SmtpEmailService service = new SmtpEmailService();
service.setHost("smtp.example.com");
service.setPort(587);
return service;
}Which Injection Style Should You Use?
| Situation | Use |
|---|---|
| Mandatory dependency | **Constructor** |
| Optional dependency with a sensible default | **Setter** with `required = false` (or `ObjectProvider` in the constructor) |
| Several implementations of an interface | Setter or constructor taking a `List<T>` |
| Configuration values | Constructor with `@Value`, or `@ConfigurationProperties` |
| Circular dependency | **Redesign.** `@Lazy` only as a last resort |
| Third-party class you can't annotate | `@Bean` method, calling its setters |
| Field injection | Avoid in application code. Acceptable in test classes |
`Objects.requireNonNull` in a constructor only helps when someone builds the object by hand. Spring never injects `null` into a required constructor parameter.
Troubleshooting Setter Injection
| Symptom | Usual cause | Fix |
|---|---|---|
| `NoSuchBeanDefinitionException` for a setter parameter | Required dependency with no bean | Define the bean, or make it optional |
| `NullPointerException` on an optional dependency | Used without a null check or default | Use a null-object default or `ObjectProvider` |
| `NullPointerException` in the constructor | Setter-injected field used before setters run | Move that logic to `@PostConstruct` |
| Setter never called | Class not a Spring bean, no matching bean with `required = false`, `static` method, or one of several parameters unresolvable | Check component scanning and the bean definition |
| `NoUniqueBeanDefinitionException` even with `required = false` | Two candidates and no `@Primary` or `@Qualifier` | Add `@Primary` or `@Qualifier` |
| Cycle error at startup after upgrading to Boot 2.6+ | Setter or field cycle no longer allowed | Redesign, or set `allow-circular-references` temporarily |
| `@Required` has no effect or won't compile | Removed in Spring 6 | Use constructor injection |
| Optional behaviour never switches on | Optional bean never created | Add `@ConditionalOnProperty` or a profile |
| Mock leaks into other tests | Shared singleton changed by reflection | Use `@MockitoBean` or `@MockBean` |
| Injected object is a different instance than expected | Prototype scope, or a proxy | Check scopes; use `ObjectProvider` for prototypes |
Common Mistakes
- Using setter injection for mandatory dependencies.
- Using `required = false` and then calling the dependency without a null check.
- Expecting `required = false` to resolve ambiguity between two beans.
- Using setter-injected dependencies in the constructor.
- Relying on setter injection to "fix" a circular dependency.
- Leaving public setters that production code calls after start-up.
- Forgetting to create the optional bean conditionally, so it is always or never present.
- Changing a shared singleton by reflection in tests.
- Adding many optional setters instead of splitting the class.