What is ObjectProvider<T> and how do getIfAvailable(), getIfUnique(), getObject(), and stream() differ? When would you choose it over plain or Optional injection?
answer
- extends ObjectFactory, also Iterable+stream (5.1)
- getObject: strict (throws none/many)
- getIfAvailable: null on none, throws on many
- getIfUnique: null on none OR many (honors @Primary)
- stream() unordered, orderedStream() @Order; prototype→fresh per getObject
basics
~20 sObjectProvider<T> is a lazy handle to a bean. getObject() throws if none/multiple; getIfAvailable() returns null if none (throws if multiple); getIfUnique() returns null if none OR multiple non-primary; stream()/orderedStream() give all matches. Use it for optional, ambiguous, lazy, or prototype lookups.
solid answer
~40 sObjectProvider<T> (extends ObjectFactory<T>) defers bean lookup to method-call time, so it injects safely even when the bean is absent or ambiguous. getObject() behaves like a strict autowire: NoSuchBeanDefinitionException if none, NoUniqueBeanDefinitionException if several. getIfAvailable() returns the bean or null when absent, but still throws on multiple non-unique candidates; getIfAvailable(Supplier) supplies a default. getIfUnique() returns the single/primary candidate, or null when there are zero OR multiple non-primary candidates — never throws on ambiguity. There are also ifAvailable(Consumer)/ifUnique(Consumer). stream() iterates all matching beans (registration order); orderedStream() applies @Order. Choose ObjectProvider over plain/Optional injection when you need: optionality plus graceful ambiguity handling, lazy resolution to break cycles or defer init, fresh prototype instances per getObject() call, or programmatic iteration/filtering of candidates.
code
java · 21 lines@Service
public class WidgetFactoryClient {
private final ObjectProvider<Widget> widgetProvider; // Widget is @Scope("prototype")
public WidgetFactoryClient(ObjectProvider<Widget> widgetProvider) {
this.widgetProvider = widgetProvider;
}
public Widget freshWidget() {
return widgetProvider.getObject(); // NEW instance every call (prototype)
}
public Widget optionalOrDefault() {
return widgetProvider.getIfAvailable(Widget::defaultWidget); // null-safe default
}
public Widget onlyIfUnambiguous() {
return widgetProvider.getIfUnique(); // null if zero OR multiple non-primary
}
}go deeper
Know ObjectProvider is a lazy handle with getIfAvailable() for optional beans.
Distinguish getObject vs getIfAvailable vs getIfUnique and stream vs orderedStream.
Articulate the ambiguity/absence matrix and the four use-cases (optional, lazy/cycle, prototype, streaming).
Weigh ObjectProvider vs @Lazy vs Optional vs @Lookup for factory access, and reason about thread-safety and non-caching semantics.
## What ObjectProvider is `org.springframework.beans.factory.ObjectProvider<T>` is a **lazy, programmatic handle** to one or more beans of type T. It extends `ObjectFactory<T>` and, since Spring 5.1, is also `Iterable<T>` and exposes `stream()`. Instead of Spring resolving the bean **at injection time** (eager), you inject the provider and resolve **on demand** by calling a method. Because resolution is deferred, the provider is always injectable — even when zero or many candidates exist. ## The method matrix | Method | Zero candidates | Exactly one | Multiple candidates | |---|---|---|---| | `getObject()` | throws `NoSuchBeanDefinitionException` | returns it | throws `NoUniqueBeanDefinitionException` (unless a @Primary) | | `getIfAvailable()` | returns `null` | returns it | throws `NoUniqueBeanDefinitionException` (unless @Primary) | | `getIfAvailable(Supplier<T>)` | returns supplier's default | returns it | throws on ambiguity | | `getIfUnique()` | returns `null` | returns it | returns `null` unless one is @Primary | | `getIfUnique(Supplier<T>)` | default | returns it | default unless @Primary | | `ifAvailable(Consumer<T>)` | no-op | runs consumer | throws on ambiguity | | `ifUnique(Consumer<T>)` | no-op | runs consumer | no-op unless @Primary | | `stream()` | empty | one element | all, registration order | | `orderedStream()` | empty | one element | all, sorted by @Order/Ordered/@Priority | **Key distinction:** `getIfAvailable` tolerates *absence* but not *ambiguity*; `getIfUnique` tolerates *both* absence and ambiguity by returning null (or honoring @Primary). ## Why choose ObjectProvider ### 1. Optional + ambiguity-safe lookup Unlike `Optional<T>` (which throws on multiple candidates), `getIfUnique()` gives you null on ambiguity — handy for "use it only if there's exactly one obvious bean." ### 2. Lazy resolution / breaking cycles Because the lookup happens when you call `getObject()`, not at construction, an `ObjectProvider` can break a circular constructor dependency: A injects `ObjectProvider<B>` and resolves B only when needed, after both beans exist. ### 3. Fresh prototype instances For a `@Scope("prototype")` bean, calling `provider.getObject()` returns a **new** instance each time — the classic way to obtain fresh prototypes from a singleton without `@Lookup` or `ApplicationContextAware`. ### 4. Programmatic iteration/filtering `stream()`/`orderedStream()` let you filter, sort, or short-circuit over all candidates without materializing a `List` field. ```java @Service class NotificationService { private final ObjectProvider<Channel> channels; NotificationService(ObjectProvider<Channel> channels) { this.channels = channels; } void send(Message m) { channels.orderedStream() .filter(c -> c.supports(m)) .findFirst() .ifPresent(c -> c.deliver(m)); } } ``` ## Edge cases & gotchas - `getObject()` and `getIfAvailable()` **both** throw on multiple non-primary candidates — only `getIfUnique()` is ambiguity-safe. - `stream()` order is **not** guaranteed; use `orderedStream()` when order matters. - `ObjectProvider` is thread-safe to hold as a field; each resolution goes back to the container. - It does **not** cache — repeated `getObject()` on a singleton returns the same singleton, but on a prototype returns fresh instances every call. - `Optional<T>` vs `ObjectProvider<T>`: choose Optional for a one-time, single, possibly-absent collaborator resolved at construction; choose ObjectProvider for laziness, defaults, prototypes, ambiguity handling, or streaming. ## Relationship to @Lazy `@Lazy T` injects a proxy that resolves on first use — similar laziness, but a **single** eager-typed proxy. `ObjectProvider` is an explicit handle giving you the full method matrix (optional/unique/stream) rather than a transparent proxy.
- Two non-primary beans of type T exist. What does getIfAvailable() do versus getIfUnique()?getIfAvailable() throws NoUniqueBeanDefinitionException — it tolerates absence but not ambiguity. getIfUnique() returns null (unless one is @Primary), tolerating both absence and ambiguity.
- How does ObjectProvider help obtain fresh prototype-scoped instances from a singleton bean?Each call to provider.getObject() re-resolves against the container, and for a prototype scope that produces a brand-new instance every call — avoiding stale single-injection and without needing @Lookup or ApplicationContextAware.
saying these in an interview costs you the question
- Saying getIfAvailable() returns null on multiple candidates (it throws)
- Claiming stream() is ordered by @Order (that's orderedStream())
- Thinking ObjectProvider caches / returns the same prototype each call
- Believing getObject() never throws