HomeSpring BootSpring Setter Injection: Optional Dependencies, @Autowired(required = false) and When to Use It
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.