Summary
EventDetails.fromProviderEventDetails(...) does not copy errorCode, so a handler
registered at API level — OpenFeatureAPI.onProviderError(Consumer<EventDetails>) —
always sees details.getErrorCode() == null, even when the provider explicitly emitted
one.
message, flagsChanged, providerName and eventMetadata all arrive intact. Only
errorCode is lost, and it is lost silently: EventDetails extends
ProviderEventDetails, so getErrorCode() compiles and returns null rather than
failing to compile.
Environment
dev.openfeature:sdk 1.22.0
Steps to reproduce
Emit an error event carrying an explicit code from any EventProvider (here a probe
extending InMemoryProvider, which is already an EventProvider):
probe.emitProviderError(ProviderEventDetails.builder()
.errorCode(ErrorCode.PROVIDER_NOT_READY)
.message("connect refused")
.build());
Log it from an API-level handler:
OpenFeatureAPI.getInstance().onProviderError(details ->
log.error("event=PROVIDER_ERROR provider={} message={} error_code={}",
details.getProviderName(), details.getMessage(), details.getErrorCode()));
Observed:
event=PROVIDER_ERROR provider=InMemoryProvider message=connect refused error_code=null
message arrives, which rules out "the event never got delivered".
Root cause
EventDetails.fromProviderEventDetails(...) is the only path from a provider-emitted
ProviderEventDetails to the EventDetails handed to API-level handlers, and its
builder chain simply does not mention errorCode (EventDetails.java on main):
static EventDetails fromProviderEventDetails(
ProviderEventDetails providerEventDetails, String providerName, String domain) {
return builder()
.domain(domain)
.providerName(providerName)
.flagsChanged(providerEventDetails.getFlagsChanged())
.eventMetadata(providerEventDetails.getEventMetadata())
.message(providerEventDetails.getMessage())
.build();
}
ProviderEventDetails has four fields; three of them are copied. Decompiling 1.22.0
shows the same five-field builder chain, so the observed behaviour and the source agree,
and the two lines of evidence are independent of each other.
Why it cannot be worked around
- API-level handlers receive
EventDetails; the original ProviderEventDetails is not
reachable from there.
- There is no public way to observe a provider's events directly —
EventProvider.setEventProviderListener and EventProvider.attach are both
package-private.
- Recovering the code by parsing
message is not viable: that text is entirely up to
each provider.
So until this is fixed, an application consuming provider events in Java has no error
code available at all.
Note: the Go SDK does not drop it
openfeature.EventDetails in the Go SDK carries ErrorCode directly, so the
equivalent handler there does receive it. This is an SDK-level divergence between the
two implementations rather than a difference in how applications are written.
Suggested fix
Add the missing line to the builder chain:
.errorCode(providerEventDetails.getErrorCode())
One line, and I checked the two things that would have made it bigger than that.
errorCode is the only field affected. ProviderEventDetails declares exactly four
fields — flagsChanged, message, eventMetadata, errorCode — and the builder chain
transfers the first three. There is no second omission, so this is a missing line rather
than a conversion that needs realigning.
Populating it does not make SDK-generated events ambiguous. The events the SDK raises
itself build a ProviderEventDetails carrying no error code
(OpenFeatureAPI.java:307 and :320), so their getErrorCode() stays null exactly as
it is today. The only thing that changes is that a code a provider explicitly set now
survives the conversion.
Still present on main
Confirmed by reading the source at 5bf9f56, not only the 1.22.0 bytecode:
EventDetails.fromProviderEventDetails still has no .errorCode(...) in its builder
chain, and all three call sites (OpenFeatureAPI.java:531, :535, :543) go through it.
Happy to open a PR.
Summary
EventDetails.fromProviderEventDetails(...)does not copyerrorCode, so a handlerregistered at API level —
OpenFeatureAPI.onProviderError(Consumer<EventDetails>)—always sees
details.getErrorCode() == null, even when the provider explicitly emittedone.
message,flagsChanged,providerNameandeventMetadataall arrive intact. OnlyerrorCodeis lost, and it is lost silently:EventDetailsextendsProviderEventDetails, sogetErrorCode()compiles and returnsnullrather thanfailing to compile.
Environment
dev.openfeature:sdk1.22.0Steps to reproduce
Emit an error event carrying an explicit code from any
EventProvider(here a probeextending
InMemoryProvider, which is already anEventProvider):Log it from an API-level handler:
Observed:
messagearrives, which rules out "the event never got delivered".Root cause
EventDetails.fromProviderEventDetails(...)is the only path from a provider-emittedProviderEventDetailsto theEventDetailshanded to API-level handlers, and itsbuilder chain simply does not mention
errorCode(EventDetails.javaonmain):ProviderEventDetailshas four fields; three of them are copied. Decompiling 1.22.0shows the same five-field builder chain, so the observed behaviour and the source agree,
and the two lines of evidence are independent of each other.
Why it cannot be worked around
EventDetails; the originalProviderEventDetailsis notreachable from there.
EventProvider.setEventProviderListenerandEventProvider.attachare bothpackage-private.
messageis not viable: that text is entirely up toeach provider.
So until this is fixed, an application consuming provider events in Java has no error
code available at all.
Note: the Go SDK does not drop it
openfeature.EventDetailsin the Go SDK carriesErrorCodedirectly, so theequivalent handler there does receive it. This is an SDK-level divergence between the
two implementations rather than a difference in how applications are written.
Suggested fix
Add the missing line to the builder chain:
One line, and I checked the two things that would have made it bigger than that.
errorCodeis the only field affected.ProviderEventDetailsdeclares exactly fourfields —
flagsChanged,message,eventMetadata,errorCode— and the builder chaintransfers the first three. There is no second omission, so this is a missing line rather
than a conversion that needs realigning.
Populating it does not make SDK-generated events ambiguous. The events the SDK raises
itself build a
ProviderEventDetailscarrying no error code(
OpenFeatureAPI.java:307and:320), so theirgetErrorCode()staysnullexactly asit is today. The only thing that changes is that a code a provider explicitly set now
survives the conversion.
Still present on
mainConfirmed by reading the source at
5bf9f56, not only the 1.22.0 bytecode:EventDetails.fromProviderEventDetailsstill has no.errorCode(...)in its builderchain, and all three call sites (
OpenFeatureAPI.java:531,:535,:543) go through it.Happy to open a PR.