What
For a single-entity federated reference resolver:
@EntityMapping
public Mono<Book> book(@Argument String id) {
return repository.findById(id); // Mono.empty() when not found
}
an empty Mono (the idiomatic reactive "not found") is turned into a hard per-entity GraphQL error — "Entity fetcher returned null or completed empty" — rather than resolving that entity to null.
Source: EntitiesDataFetcher.invokeEntityMethod:
return handlerMethod.getEntity(environment, representation)
.switchIfEmpty(Mono.error(new RepresentationNotResolvedException(representation, handlerMethod)))
.onErrorResume((ex) -> resolveException(ex, environment, handlerMethod, index));
Why this is arguably wrong
-
The Apollo Federation subgraph spec defines _entities(representations: [_Any!]!): [_Entity]! — the list is non-null but its elements are nullable, and the spec states: "Entries in the list can be null if no entity exists for a provided representation." So resolving an unresolvable representation to null is spec-compliant; erroring is stricter than the spec.
-
Mono.empty() is the natural reactive signal for "absent"; converting it to an error is surprising.
-
Inconsistency within the framework: the batch form (List<T> / Mono<List<T>>) already lets a null list entry become a null entity with no error — EntitiesDataFetcher.applyResults does entities.set(index, null) for a non-ErrorContainer (null) result. So the framework can already emit spec-legal nulls; only the ergonomic single-Mono form cannot.
Impact
A subgraph that references another subgraph's entity by a key that may legitimately not resolve (e.g. content deleted after the reference was recorded) cannot return a spec-compliant null from the single-entity form. It must either pre-validate every key or switch to the batch form purely to obtain null tolerance. At the gateway the per-entity error is surfaced to clients (e.g. graphql-request throws on any GraphQL error, failing the whole operation).
Proposed change
Let the single-entity @EntityMapping (Mono<T>) resolve an empty Mono to a null entity — matching the nullable _Entity element and the batch form — or provide an opt-in for that behaviour.
Repro
An @EntityMapping Mono<T> returning Mono.empty() yields the error "Entity fetcher returned null or completed empty" instead of a null entity.
Environment
- Spring for GraphQL (Spring Boot 4.0.x)
- Apollo Federation v2 (
FederationSchemaFactory)
References
EntitiesDataFetcher.invokeEntityMethod (single) vs applyResults (batch) — spring-graphql/src/main/java/org/springframework/graphql/data/federation/EntitiesDataFetcher.java
- Apollo Federation subgraph spec,
_entities field
Disclaimer
This summary was created with the help of AI during a debugging session on why optional entities throw errors
What
For a single-entity federated reference resolver:
an empty Mono (the idiomatic reactive "not found") is turned into a hard per-entity GraphQL error —
"Entity fetcher returned null or completed empty"— rather than resolving that entity to null.Source:
EntitiesDataFetcher.invokeEntityMethod:Why this is arguably wrong
The Apollo Federation subgraph spec defines
_entities(representations: [_Any!]!): [_Entity]!— the list is non-null but its elements are nullable, and the spec states: "Entries in the list can be null if no entity exists for a provided representation." So resolving an unresolvable representation tonullis spec-compliant; erroring is stricter than the spec.Mono.empty()is the natural reactive signal for "absent"; converting it to an error is surprising.Inconsistency within the framework: the batch form (
List<T>/Mono<List<T>>) already lets anulllist entry become a null entity with no error —EntitiesDataFetcher.applyResultsdoesentities.set(index, null)for a non-ErrorContainer(null) result. So the framework can already emit spec-legal nulls; only the ergonomic single-Monoform cannot.Impact
A subgraph that references another subgraph's entity by a key that may legitimately not resolve (e.g. content deleted after the reference was recorded) cannot return a spec-compliant
nullfrom the single-entity form. It must either pre-validate every key or switch to the batch form purely to obtain null tolerance. At the gateway the per-entity error is surfaced to clients (e.g.graphql-requestthrows on any GraphQL error, failing the whole operation).Proposed change
Let the single-entity
@EntityMapping(Mono<T>) resolve an emptyMonoto anullentity — matching the nullable_Entityelement and the batch form — or provide an opt-in for that behaviour.Repro
An
@EntityMapping Mono<T>returningMono.empty()yields the error "Entity fetcher returned null or completed empty" instead of a null entity.Environment
FederationSchemaFactory)References
EntitiesDataFetcher.invokeEntityMethod(single) vsapplyResults(batch) —spring-graphql/src/main/java/org/springframework/graphql/data/federation/EntitiesDataFetcher.java_entitiesfieldDisclaimer
This summary was created with the help of AI during a debugging session on why optional entities throw errors