At a glance
- Identifier: wg#1871
- Stage: RFC 0 / Strawman
- Champion: @nikolayandr
- Latest activity: WG discussion created on 2025-12-04
- WG discussion: https://github.com/graphql/graphql-wg/discussions/1871
WG discussion
Hi all,
I would like to propose an execution-level idea intended to address ambiguity around null values produced by resolver errors in GraphQL responses. This proposal does not introduce any new syntax or type modifiers; it is an optional execution mode that keeps GraphQL fully backwards-compatible.
This discussion is intentionally lightweight — the goal is to validate the idea and get initial feedback before considering a formal RFC.
💡 Proposal (optional execution mode)
If a resolver fails, omit the field entirely from the data response. The failure is reported only in errors[], not as a null value.
This applies to:
- scalar fields
- object fields
- elements of lists
- nested fields
Null bubbling is skipped in this opt-in mode.
🎯 Problem
When a resolver fails today, GraphQL behaves in one of two ways depending on the field’s nullability:
- If the field is nullable, the resolver error produces:
{ "field": null }
and an entry in errors[].
- If the field is non-null (!), the specification requires null bubbling:
- The field itself cannot be returned as null
- The nearest nullable parent is forced to become null
- Bubbling continues upward until a nullable parent is found (or the entire data becomes null)
Example:
type Product {
price: Float!
name: String
}
type Query {
product: Product
}
If the price resolver fails, the response must be:
{
"data": { "product": null },
"errors": [
{ "message": "timeout", "path": ["product","price"] }
]
}
This behavior is mandatory in the current GraphQL specification. Servers cannot avoid bubbling while remaining spec-compliant.
The core issue
A client receiving:
"price": null
cannot distinguish, from the data section alone, whether:
- the application legitimately returned null (semantic null), or
- the resolver failed, or etc
Yes, clients can inspect the errors[] array, but in practice it adds complexity
This is the ambiguity the proposal aims to address.
💡 Proposed: optional “omit-on-error” execution mode
If a resolver fails, omit the field entirely from data. The failure is reported only in errors[], not by returning null.
Key points:
- No changes to schema syntax
- No additional nullability operators
- No changes to existing nullability semantics
- Fully backward-compatible (opt-in)
- Null bubbling is not performed under this mode
This mode introduces a clearer separation between:
- semantic nulls, and
- resolver failures, which become missing fields.
📘 Examples
- Nullable field Today:
{
"data": { "product": { "sku": "SKU1", "price": null } },
"errors": [
{ "message": "timeout", "path": ["product","price"] }
]
}
Proposed:
{
"data": { "product": {"sku": "SKU1"} },
"errors": [
{ "message": "timeout", "path": ["product","price"] }
]
}
- Non-null field (null bubbling vs omission) Today — required null bubbling:
{
"data": { "product": null },
"errors": [
{ "message": "timeout", "path": ["product","price"] }
]
}
Proposed (in the optional execution mode):
{
"data": {
"product": { "sku": "SKU1" }
},
"errors": [
{ "message": "timeout", "path": ["product","price"] }
]
}
The parent object remains intact. The problematic field is simply omitted.
- Lists Return an empty object {} at that index (to preserve list shape) and report the error:
{
"data": {
"products": [
{ sku: "SKU1", "price": 1.0 },
{}
]
},
"errors": [
{ "message": "failed", "path": ["products",1] }
]
}
If only a nested field fails, only that field is omitted:
{
"data": {
"products": [
{ sku: "SKU1", "price": 1.0 },
{ sku: "SKU2"}
]
},
"errors": [
{ "message": "failed", "path": ["products",1, "price"] }
]
}
🟩 Benefits
- Eliminates ambiguity between semantic null and resolver-failure null
- Allows interpreting data without always correlating with errors[]
- Makes schema nullability reflect real data semantics
- Great for typed clients (TypeScript, Flow, Swift, Kotlin)
- Does not introduce new syntax or type modifiers
- Backward-compatible as long as it is activated explicitly by the client (per-request opt-in).
- Easier to reason about partial responses
Timeline
- WG discussion created on 2025-12-04 by nikolayandr