skip to content

In GraphQL, why does adding a non-null argument with no default break existing documents?

level: middleimportance: must knowfreq 51%

answer

  1. Ask what the server checks first
  2. A validation rule about arguments
  3. Non-null is not the same as required
  4. A default value exempts the argument
  5. The whole request fails, not one field

basics

~20 s

Validation requires that every argument whose type is non-null and which declares no default value is present in the document. Deployed documents omit the new argument, so they fail validation and the whole request is rejected before execution.

solid answer

~50 s

GraphQL's required-arguments validation rule says that for every field in a document, an argument whose type is non-null **and which declares no default value** must be present and must not be the literal `null`. Add such an argument to `Query.trialsBySite` in a clinical-trial registry and every deployed document selecting that field instantly violates the rule — including documents from teams that will never use the new argument, because the requirement attaches to the field they selected. Validation runs before execution, so nothing resolves: the response carries `errors` and no data at all, not a partial result. The same trap exists one level down: adding a non-null field with no default to an input object breaks literal input values by an equivalent rule, and breaks variable-supplied values during variable coercion. The fix is to make the argument nullable or give it a default value — an argument with a default is not *required* even though its type is non-null.

code

graphql · 3 lines
graphql
type Query {
  trialsBySite(siteId: ID!, protocolVersion: String!): [Trial!]!
}

go deeper

for a junior

Remember the headline: an argument that is non-null with no default must be sent, so adding one to an existing field breaks callers who never asked for it. Adding a nullable argument is the safe version.

for a middle

Explain the two conditions of the rule and why a default value defuses it, and say where the failure lands — validation, before execution, whole request rejected with no partial data.

for a senior

Be able to trace the same class through input objects and variables, where the failure moves from document validation to variable coercion, and to say what you would ship instead when the new input is genuinely mandatory.

for a principal

Frame the choice: a mandatory new input is a new capability, so decide whether it belongs on the existing field with a compatibility default or on a new one, and who carries the migration cost either way.

## The rule that does the breaking GraphQL's validation rules include one about required arguments: for every field and directive in a document, any argument whose type is non-null **and which declares no default value** must be present, and its value must not be the literal `null`. That single rule is the whole mechanism. The moment the schema declares such an argument, every document that selects the field without supplying it is invalid — not deprecated, not degraded, invalid. The blast radius surprises people because it has nothing to do with intent. Consider a clinical-trial registry: ```graphql type Query { trialsBySite(siteId: ID!): [Trial!]! } ``` Add `protocolVersion: String!` to it and a deployed document like ```graphql query SiteTrials($siteId: ID!) { trialsBySite(siteId: $siteId) { id phase } } ``` stops validating. The team that owns that document never asked for `protocolVersion` and may not know it exists. They are broken anyway, because the requirement is attached to the *field*, and selecting the field is the only thing they did. ## Non-null is not the same as required The rule turns on two conditions, and the second is the escape hatch. An argument declared `String!` with a default value is non-null but **not required**: the document may omit it, and the server substitutes the default during argument coercion. So ```graphql type Query { trialsBySite(siteId: ID!, protocolVersion: String! = "v3"): [Trial!]! } ``` is a safe addition, while the same argument without `= "v3"` is not. Candidates who have internalised "non-null means the client must send it" get this backwards; the question is always whether a default exists to stand in. The other safe shape is simply a nullable argument, `protocolVersion: String`, which the server then has to handle as absent. Which of the two you pick is a modelling decision — a default says "there is a sensible value", nullable says "absence is meaningful" — but both preserve every deployed document. ## Where the failure lands Validation runs before execution, so nothing resolves at all. The response has no partial data: an `errors` entry describing the missing argument, and `data` absent entirely. That is important operationally, because it means the failure is total per request rather than confined to one branch of the response. A screen that happened to select the affected field alongside eleven healthy fields shows nothing, not eleven-twelfths of itself. It also means the failure is *deterministic and instant*. There is no window in which some requests work. The first request sent after the schema is deployed fails, from every client, whether or not that client has redeployed. ## The input-object version of the same trap The same class of edit exists one level down. Adding a non-null field with no default to an input object type — say `consentVersion: String!` on `EnrollParticipantInput` — breaks every caller that constructs that input. There are two paths to the failure and both end in a rejected request: * When the input is written as a **literal** in the document, a validation rule about required input-object fields rejects it, exactly as with arguments. * When the input arrives through **variables**, the document itself is fine — it just says `$input: EnrollParticipantInput!`. The failure happens during variable coercion, before execution, and surfaces as a request error rather than a field error. That difference matters when you are reading an incident report: the same schema edit produces two different error messages depending on how the caller happened to build its input, and neither one names your new field in the client's own source. ## The subtler variable rule There is a related rule people meet less often. A document may pass a *nullable* variable into a non-null argument only when the variable declares a non-null default value, or the argument location itself has a default. So a client that dutifully starts sending the new argument, but declares it as `$protocolVersion: String` rather than `String!`, still fails validation. When someone reports "I added the argument and it still doesn't work", this is usually why. ## What to do instead If the argument genuinely must be mandatory for new behaviour, give it a default value that reproduces the old behaviour, or make it nullable and treat absence as the old behaviour. If neither is honest — the field simply cannot be answered without the new input — then the new requirement is a new field, not an edit to the old one, and the old field's retirement becomes a separate decision with its own timeline.

  • Does giving the new argument a default value make it safe to add?
    Yes. The rule requires an argument only when its type is non-null *and* it has no default value. With a default, a document may omit it and the server substitutes the default during argument coercion, so every deployed document keeps validating. Choose the default so it reproduces the previous behaviour, otherwise you have kept the documents valid while silently changing what they return.
  • A client starts sending the new argument but declares its variable as nullable. Is that document valid?
    Not by default. A validation rule governs variable usages: a nullable variable may be used at a non-null location only when the variable declares a non-null default value, or the argument location itself has one. So `$protocolVersion: String` passed into `protocolVersion: String!` is rejected even though a value will be supplied at runtime — the client must declare `String!`.
  • Where does the failure surface when a new required input-object field arrives through variables rather than a literal?
    In variable coercion rather than document validation. The document only declares `$input: EnrollParticipantInput!` and stays syntactically fine; coercing the supplied JSON against the input type fails because a non-null field with no default is missing. That is still a request error before execution, so the response has `errors` and no data — but the message points at the variable, not at a line in the document.

saying these in an interview costs you the question

  • Thinks a missing required argument resolves to null
  • Says only clients that want the new argument are affected
  • Confuses non-null with required and ignores default values
  • Expects a partial response with the other fields intact
  • Believes variables let a client skip a required argument

context