skip to content

How do you declare a nullable field in OpenAPI 3.0 versus OpenAPI 3.1?

level: middleimportance: must knowfreq 54%

answer

  1. 3.0 needed its own keyword
  2. 3.1 borrows the standard's type array
  3. Quote it, or YAML eats it
  4. Silently ignored after the version bump
  5. Reference siblings behave differently per version

basics

~10 s

OpenAPI 3.0 uses its own keyword, nullable: true, alongside a single type. OpenAPI 3.1 removed nullable and follows JSON Schema 2020-12, where you write a type array such as type: [string, "null"].

solid answer

~40 s

In OpenAPI 3.0 a schema has exactly one `type`, so nullability needed an OpenAPI-specific extension: `type: string` plus `nullable: true`. That keyword does not exist in JSON Schema, which is part of why 3.0 schemas were not portable. OpenAPI 3.1 aligns the Schema Object with JSON Schema 2020-12, where `type` may be an array — so you write `type: ["string", "null"]`, and `nullable` is gone entirely. A 3.1 document containing `nullable: true` is not honoured: at best a linter warns, at worst tooling silently ignores it and your field stops accepting null. Two related gotchas: in 3.0, sibling keys next to a `$ref` are ignored, so making a referenced schema nullable needs an `allOf` wrapper, whereas 3.1 permits `$ref` siblings directly; and nullability is independent of `required`, which only governs key presence.

code

yaml · 9 lines
yaml
# OpenAPI 3.0.3
closedAt:
  type: string
  format: date-time
  nullable: true
address:
  allOf:
    - $ref: '#/components/schemas/Address'
  nullable: true

go deeper

for a junior

Recall the two spellings and which version each belongs to: nullable: true in 3.0, type: [string, "null"] in 3.1.

for a middle

Explain why the change happened — 3.1's alignment with JSON Schema 2020-12 — and that nullability and requiredness are independent axes.

for a senior

Show the migration risk you would guard against: nullable silently becoming a no-op after the version bump, and generated clients turning an explicit null into an omitted field on PATCH.

for a principal

Own the null policy across the estate — whether null is allowed to carry meaning at all, how PATCH semantics are expressed, and how a version migration is linted before it ships.

## Why the two versions differ at all OpenAPI 3.0's Schema Object was a modified subset of an old JSON Schema draft in which `type` held exactly one value. There was no way to say "a string or null". OpenAPI therefore added its own keyword, `nullable`, as an extension to the subset. OpenAPI 3.1 realigned the Schema Object with JSON Schema 2020-12, which supports `type` as an array of type names including `"null"`. With native support available, the bespoke keyword was removed. ## The 3.0 form ``` closedAt: type: string format: date-time nullable: true ``` `nullable: true` widens the declared type to also permit the JSON literal `null`. It defaults to `false`, so an ordinary `type: string` rejects null. ## The 3.1 form ``` closedAt: type: [string, "null"] format: date-time ``` Quote `"null"` in YAML — unquoted `null` parses as the YAML null value rather than the string `"null"`, and the schema then makes no sense. An equivalent but more verbose spelling is `oneOf: [{type: string}, {type: "null"}]`, which is sometimes needed when the null branch must sit next to other composition. ## The migration trap When you bump `openapi` from `3.0.3` to `3.1.0`, every `nullable: true` in the document becomes a no-op. `nullable` is not part of the 3.1 vocabulary; JSON Schema 2020-12 ignores keywords it does not know, so validation quietly narrows — a field that used to accept null now rejects it, and the failure appears at runtime in whichever consumer sends null first. Linters such as Spectral ship rules that flag `nullable` in a 3.1 document, and turning that rule on before the bump is the cheapest safeguard. ## nullable next to $ref This is the second-most-asked follow-up. In OpenAPI 3.0, a Reference Object's siblings are ignored: writing ``` address: $ref: '#/components/schemas/Address' nullable: true ``` does not make the referenced schema nullable — the `$ref` wins and the sibling is dropped. The 3.0 workaround is to wrap it: ``` address: allOf: - $ref: '#/components/schemas/Address' nullable: true ``` The single-element `allOf` exists purely to give `nullable` a place to live. This is why 3.0 documents are littered with one-branch `allOf` wrappers that also carry `description` or `example`. In OpenAPI 3.1, Schema Objects follow JSON Schema 2020-12, where `$ref` may coexist with sibling keywords, so the wrapper is unnecessary for schema-level annotations. ## nullable is not optional Nullability and requiredness are separate axes. `required: [closedAt]` with a nullable schema means "the key must be present, and null is a legitimate value". Dropping the field from `required` means "the key may be absent". Modelling both — optional *and* nullable — leaves consumers unable to distinguish absent from null unless their client library exposes a tri-state, which most do not. Decide which of the two states you actually need. ## What generators do Generators map nullability to the target language's null handling: a nullable field becomes a nullable type, an `Optional`, or a type union. Where it bites is round-tripping — a generated client that deserializes null into a language null and then re-serializes may emit the field as absent rather than null, silently converting "clear this value" into "leave it unchanged" on a PATCH. If null carries meaning in your API, test the round trip through the generated client rather than trusting the schema alone. ## Neighbouring 3.1 changes worth knowing The `nullable` removal is one item in a family of JSON Schema realignments: `exclusiveMinimum` and `exclusiveMaximum` become numbers in 3.1 rather than booleans modifying `minimum`/`maximum`, and the Schema Object's singular `example` is deprecated in favour of the JSON Schema `examples` array. If you are auditing a document for a 3.1 move, sweep for all of these together — they are the changes a mechanical converter is most likely to leave behind.

  • What happens to nullable: true when you bump a document from 3.0 to 3.1?
    It becomes a no-op. `nullable` is not in the 3.1 vocabulary, and JSON Schema 2020-12 ignores unknown keywords, so the field silently stops accepting null. Lint for `nullable` before the bump and rewrite each occurrence as a type array.
  • Why does nullable next to a $ref not work in OpenAPI 3.0?
    Because 3.0 treats a Reference Object's siblings as ignored — the `$ref` replaces the object wholesale. The workaround is a single-element `allOf` wrapping the `$ref` with `nullable` beside it. OpenAPI 3.1 permits `$ref` siblings directly, so the wrapper disappears.
  • Is a nullable field the same as an optional one?
    No. Nullability is about the value; requiredness is about whether the key appears. A required nullable field must be present and may be null; an optional field may be missing entirely. Modelling both leaves consumers unable to distinguish absent from null.

saying these in an interview costs you the question

  • Keeps nullable: true in a 3.1 document
  • Writes unquoted null in a YAML type array
  • Puts nullable beside a $ref in OpenAPI 3.0
  • Says nullable makes a field optional
  • Assumes 3.0 type can be an array

context