Context
We conducted a cross-SDK comparison of all MultiProvider implementations using the js-sdk as the reference. The Java MultiProvider was recently moved into the core SDK in #1765, and while functional for basic use cases, we identified several gaps relative to the reference implementation. Some of these were already noted during the original PR review.
Gaps
1. Child provider event aggregation and status tracking (High)
The MultiProvider extends EventProvider but does not listen to or forward events from child providers. If a child provider emits PROVIDER_ERROR, PROVIDER_STALE, or PROVIDER_CONFIGURATION_CHANGED at runtime, those events are not surfaced. This was called out in the #1765 review by @guidobrei:
"we effectively lose the Event features when using MultiProvider"
Expected behavior:
- Listen to each child provider's events
- Maintain a per-provider status map
- Compute an aggregate status using "worst-wins" precedence:
FATAL > NOT_READY > ERROR > STALE > READY
- Emit the corresponding event when the aggregate status changes
- Always forward
PROVIDER_CONFIGURATION_CHANGED events (pass-through)
Reference: js-sdk status-tracker.ts, dotnet-sdk HandleProviderEventAsync / DetermineAggregateStatus
2. Per-provider hook execution during evaluation (High)
The strategy calls provider evaluation methods directly (e.g. provider.getBooleanEvaluation(...)), bypassing the SDK's hook pipeline. If a child provider defines hooks via getProviderHooks(), those hooks are not executed.
Expected behavior:
- Before evaluating a child provider, run its
before hooks with an isolated copy of the hook context
- On success: run
after hooks
- On error: run
error hooks
- Always: run
finally hooks
- Hook context must be isolated per-provider to prevent cross-provider mutation
Reference: js-sdk hook-executor.ts, go-sdk isolation.go, dotnet-sdk ProviderExtensions.EvaluateAsync
3. Tracking event forwarding (High)
track() is not overridden. The default no-op implementation means tracking events are not forwarded to child providers.
Expected behavior:
- Iterate over child providers and forward
track() calls
- The strategy should control which providers receive tracking (e.g. skip
NOT_READY / FATAL providers)
- Errors from individual
track() calls should be caught and logged, not propagated
Reference: js-sdk multi-provider.ts track(), dotnet-sdk MultiProvider.cs Track()
4. ComparisonStrategy (Medium)
Only FirstMatchStrategy and FirstSuccessfulStrategy exist. There is no ComparisonStrategy for evaluating all providers and comparing results (useful for migration validation and consistency checks).
Expected behavior:
- Evaluate all providers (ideally in parallel)
- If all providers agree on the value, return it
- If providers disagree, call an optional
onMismatch callback and return the designated fallback provider's result
- If any provider errors, collect and report all errors
- Constructor accepts a
fallbackProvider and optional onMismatch callback
Reference: js-sdk comparison-strategy.ts, go-sdk comparison_strategy.go, dotnet-sdk ComparisonStrategy.cs
5. Duplicate provider name handling (Medium)
When two child providers share the same metadata.name, the later provider silently overwrites the earlier one in the internal LinkedHashMap. The first provider is effectively lost. Related to #1792.
Expected behavior:
- If an explicit name conflicts with an existing name, throw (misconfiguration)
- If multiple providers share the same metadata-derived name, auto-deduplicate with a numeric suffix (
name-1, name-2, etc.)
- All providers should be preserved
Reference: js-sdk registerProviders(), dotnet-sdk RegisterProviders()
Spec Reference
https://openfeature.dev/specification/appendix-a/#multi-provider
Context
We conducted a cross-SDK comparison of all MultiProvider implementations using the js-sdk as the reference. The Java MultiProvider was recently moved into the core SDK in #1765, and while functional for basic use cases, we identified several gaps relative to the reference implementation. Some of these were already noted during the original PR review.
Gaps
1. Child provider event aggregation and status tracking (High)
The MultiProvider extends
EventProviderbut does not listen to or forward events from child providers. If a child provider emitsPROVIDER_ERROR,PROVIDER_STALE, orPROVIDER_CONFIGURATION_CHANGEDat runtime, those events are not surfaced. This was called out in the #1765 review by @guidobrei:Expected behavior:
FATAL > NOT_READY > ERROR > STALE > READYPROVIDER_CONFIGURATION_CHANGEDevents (pass-through)Reference: js-sdk
status-tracker.ts, dotnet-sdkHandleProviderEventAsync/DetermineAggregateStatus2. Per-provider hook execution during evaluation (High)
The strategy calls provider evaluation methods directly (e.g.
provider.getBooleanEvaluation(...)), bypassing the SDK's hook pipeline. If a child provider defines hooks viagetProviderHooks(), those hooks are not executed.Expected behavior:
beforehooks with an isolated copy of the hook contextafterhookserrorhooksfinallyhooksReference: js-sdk
hook-executor.ts, go-sdkisolation.go, dotnet-sdkProviderExtensions.EvaluateAsync3. Tracking event forwarding (High)
track()is not overridden. The default no-op implementation means tracking events are not forwarded to child providers.Expected behavior:
track()callsNOT_READY/FATALproviders)track()calls should be caught and logged, not propagatedReference: js-sdk
multi-provider.tstrack(), dotnet-sdkMultiProvider.csTrack()4. ComparisonStrategy (Medium)
Only
FirstMatchStrategyandFirstSuccessfulStrategyexist. There is noComparisonStrategyfor evaluating all providers and comparing results (useful for migration validation and consistency checks).Expected behavior:
onMismatchcallback and return the designated fallback provider's resultfallbackProviderand optionalonMismatchcallbackReference: js-sdk
comparison-strategy.ts, go-sdkcomparison_strategy.go, dotnet-sdkComparisonStrategy.cs5. Duplicate provider name handling (Medium)
When two child providers share the same
metadata.name, the later provider silently overwrites the earlier one in the internalLinkedHashMap. The first provider is effectively lost. Related to #1792.Expected behavior:
name-1,name-2, etc.)Reference: js-sdk
registerProviders(), dotnet-sdkRegisterProviders()Spec Reference
https://openfeature.dev/specification/appendix-a/#multi-provider