At a glance
- Identifier: #1065
- Stage: RFC 0 / Strawman
- Champion: @benjie
- Latest activity: Added to WG agenda on 2025-04-03
- Spec PR: https://github.com/graphql/graphql-spec/pull/1065
- Related:
Spec PR description
TL;DR: Introduces a new type wrapper, Semantic-Non-Null, which represents that a value will not be null unless an error happens, and if an error does happen then this null does not bubble.
GraphQL.js implementation: https://github.com/graphql/graphql-js/pull/4192
The problem
GraphQL schema designers must use non-nullable types sparingly because if a non-nullable type were to raise an error then the entire selection set it is within will be destroyed, leading to clients receiving less usable data and making writing the results to a normalized cache a dangerous action. Because of this, nullable-by-default is a best practice in GraphQL, and non-null type wrappers should only be used for fields that the schema designer is confident will never raise an error - not just in the current schema, but in all future schemas.
Many GraphQL consumers choose to ignore the entire response from the server when any error happens, one reason for this is because the null bubbling behavior makes writing to normalized caches dangerous. For these users, when an error doesn't happen, the nullable fields they are dealing with can be frustrating because their type generation requires them to handle the null case even if it may never happen in practice, which can lead to a lot of unnecessary code that will never execute. There is currently no way for the type generators to know that a field will never be null unless there's an associated error.
The solution
We can categorise that there are effectively two types of null:
- Error
null: where a position isnulland there's a related error (with matching or prefixed path) in theerrorslist - indicates that something went wrong. - Semantic
null: where a position isnulland there is no related error - this data truly is null (e.g. a user having not yet set their avatar may haveavatar: null; this is not an error).
This PR introduces a new wrapper type in addition to List and Non-Null, called Semantic-Non-Null. The Semantic-Non-Null type indicates that the field will never be a semantic null - it will not be null in the normal course of business, but can be null only if accompanied by an error in the errors list (i.e. an "error null"). Thus a client that throws out all responses with errors will never see a null in this position. Also, critically, any null raised by this field will not bubble and thus if an error is found with the exact path to this null then it is safe to store the result (including the error) into a normalized cache.
In SDL the Semantic-Non-Null wrapper is currently represented by an asterisk (*) suffix (as opposed to the ! suffix for a strict Non-Null).
Thus we have the following:
| # | Type description | Syntax | Result values |
|---|---|---|---|
1 | Unadorned String | String | string, or error null, or semantic null |
2 | Semantic-Non-Null String | String* | string, or error null |
3 | (Strict-)Non-Null String | String! | string |
Note that 1 and 3 above are exactly the same as in the current GraphQL specification, this PR introduces 2 which sits in the middle.
Backwards compatibility
All existing schemas are automatically supported because the meaning of String and String! is unchanged.
To ensure that all existing clients are automatically supported, this PR introduces the includeSemanticNonNull argument on __Field.type which defaults to false. Clients that do not pass includeSemanticNonNull: true will see all Semantic-Non-Null types stripped, which will have the effect of making them appear as if they were the unadorned types. This is safe, since it means these clients will need to handle both error nulls and semantic nulls (as they traditionally would have) even though we know that a semantic null will never happen in practice.
All existing GraphQL documentation, tutorials, examples, and everything else we've built over the last 8 years remains valid since the meaning of String and String! are unchanged.
History
This PR is almost identical to #1048, but it changes the name of the new type wrapper from Null-Only-On-Error to Semantic-Non-Null. It addresses the True Nullability Schema discussion raised by @captbaritone and incorporates/adapts some of the terminology from @leebyron's Strict Semantic Nullability proposal.
Timeline
- Added to WG agenda on 2025-04-03
- 6 commits pushed on 2025-03-10:
- benjie committed "Oops, missed one"
- benjie committed "Add more clarifying text"
- benjie committed "Change syntax to use asterisk again"
- benjie committed "Fix bug in RecursivelyStripSemanticNonNullTypes"
- benjie committed "Merge branch 'main' into semantic-non-null"
- benjie committed "Fix formatting"
- Commit pushed on 2024-01-03 by benjie: Add IsValidImplementationFieldType updates
- Spec PR created on 2023-11-24 by benjie
- Commit pushed on 2023-11-24 by benjie: Change name to 'SemanticNonNull' and syntax to bang prefix
- 3 commits pushed on 2023-10-04:
- benjie committed "Add specification changes for Null-Only-On-Error type"
- benjie committed "Add examples combining null-only-on-error with list and non-null"
- benjie committed "Remove duplicate coercion for brevity"