skip to content

How do you model optional dependencies and disambiguate multiple constructors across the three injection styles?

level: seniorimportance: should knowfreq 45%

answer

  1. optional: setter required=false / Optional<T> / @Nullable / ObjectProvider
  2. ObjectProvider = lazy getIfAvailable/getIfUnique
  3. single ctor auto-wired; many -> @Autowired one
  4. greedy: several @Autowired(required=false) constructors
  5. List<T>/Map<String,T> = optional-many

basics

~10 s

Optional deps: setter with @Autowired(required=false), or constructor param typed Optional<T> or annotated @Nullable. Multiple constructors: mark the one to use with @Autowired (single-ctor auto-wiring only works when there's exactly one).

solid answer

~40 s

For optional dependencies, setter injection is the classic fit: @Autowired(required=false) leaves the field null if no bean exists. With constructor injection you keep the dependency optional by declaring the parameter as Optional<T>, @Nullable T, or ObjectProvider<T> — Spring injects an empty Optional or null rather than failing. Prefer ObjectProvider for lazy/optional lookups and to iterate over zero-or-many candidates. For multiple constructors, Spring's implicit single-constructor auto-wiring only applies when there's exactly one constructor; if there are several you must annotate exactly one with @Autowired, or optionally mark several with @Autowired(required=false) and Spring picks the greediest satisfiable one. A common real-world pattern is one @Autowired constructor for the container and a second plain constructor for tests. Field injection can also use required=false but inherits all the usual drawbacks.

code

java · 24 lines
java
@Service
class NotificationService {
    private final SmsSender sms;                 // required
    private final ObjectProvider<PushSender> push; // optional / lazy

    NotificationService(SmsSender sms, ObjectProvider<PushSender> push) {
        this.sms = sms;
        this.push = push;
    }

    void notifyUser(String msg) {
        sms.send(msg);
        push.ifAvailable(p -> p.send(msg)); // only if a PushSender bean exists
    }
}

// Multiple constructors: Spring won't guess — annotate the one to use
@Service
class ReportService {
    private final Clock clock;
    @Autowired
    ReportService(Clock clock) { this.clock = clock; }   // used by container
    ReportService() { this(Clock.systemUTC()); }          // convenience/test ctor
}

go deeper

for a junior

Likely only knows setter injection for optional deps.

for a middle

Knows Optional<T>/@Nullable and the single-ctor rule.

for a senior

Adds ObjectProvider semantics and multi-constructor greedy resolution.

for a principal

Chooses between Optional/ObjectProvider deliberately for laziness, cycles, and startup behavior.

**Modeling optional dependencies.** 1. **Setter injection**: `@Autowired(required=false)` on the setter — Spring calls it only if a matching bean exists; otherwise the field stays at its default (`null`). Classic for reconfigurable/optional collaborators. 2. **Constructor injection, still optional**: - `Optional<PaymentGateway>` parameter — Spring injects `Optional.empty()` when no bean is present. - `@Nullable PaymentGateway` (or `@Autowired(required=false)` on the constructor) — Spring injects `null` rather than failing. - `ObjectProvider<PaymentGateway>` — a lazy handle: call `.getIfAvailable()`, `.getIfUnique()`, or iterate. Best for optional-or-many and to avoid eager resolution / break cycles. 3. **Collections**: injecting `List<Handler>` or `Map<String, Handler>` gathers all matching beans (empty collection if none) — a form of "optional many." **Disambiguating multiple constructors.** - **Single constructor** (since Spring 4.3): no annotation needed — Spring auto-wires it. - **Multiple constructors**: Spring will **not** guess. You must mark exactly one with `@Autowired` (its `required` defaults to `true`). - **Advanced**: you may annotate *several* constructors with `@Autowired(required=false)`; Spring then treats them as candidates and chooses the one with the **most satisfiable arguments** (greedy). Only one such candidate may have `required=true` — mixing a `required=true` with other `@Autowired` constructors is an error. - **Common pattern**: annotate the production constructor with `@Autowired` and provide an extra unannotated constructor purely for tests or convenience. **Gotchas.** - Forgetting `@Autowired` when you add a second constructor: Spring may fall back to a no-arg constructor (if present) and leave dependencies unset, or fail with `no default constructor found`. - `@Autowired(required=false)` on a **constructor** changes the whole bean's resolution semantics — if the args can't be satisfied Spring may skip that constructor entirely. - Ambiguous bean type at an injection point still needs `@Qualifier` or `@Primary`, independent of the injection style. **Key APIs.** `@Autowired(required=false)`, `@Nullable` (`org.springframework.lang.Nullable` or JSR-305), `java.util.Optional`, `org.springframework.beans.factory.ObjectProvider`, `@Qualifier`, `@Primary`.

  • When would you inject ObjectProvider<T> instead of Optional<T> or @Nullable T?
    When you want lazy resolution (defer the lookup until actually needed), to handle zero-or-many candidates via iteration or getIfUnique(), or to avoid eager instantiation that could trigger a circular dependency at startup.
  • What happens if a bean has two constructors and neither is annotated with @Autowired?
    Spring won't auto-wire either; it falls back to a no-arg/default constructor if one exists (leaving dependencies unset), otherwise bean creation fails. You must annotate exactly one constructor to disambiguate.

saying these in an interview costs you the question

  • Thinking Spring auto-picks a constructor when there are several unannotated ones
  • Believing constructor injection can't express optional dependencies
  • Confusing @Autowired(required=false) with @Qualifier — one is optionality, the other disambiguation by name/type

context