OpenAPI 3.0 vs. 3.1: What Actually Changed
OpenAPI 3.1 isn't a superset of 3.0 — a handful of 3.0 patterns are now invalid, and the reason for all of them is the same underlying decision: full alignment with JSON Schema.
The One Decision Behind Every Change
OpenAPI 3.1.0, released in February 2021, made a single architectural decision that explains nearly every difference from 3.0: the Schema Object is no longer a loose, OpenAPI-specific dialect inspired by JSON Schema — it is JSON Schema, full stop, specifically the 2020-12 draft. Every valid JSON Schema document is now a valid OpenAPI 3.1 schema. That sounds like a minor internal detail, but it breaks a handful of patterns that were idiomatic in 3.0, because OpenAPI 3.0's Schema Object was actually a constrained subset loosely based on JSON Schema Draft 4 with a few OpenAPI-only additions bolted on (nullable, discriminator, example). Draft 4 and 2020-12 disagree on how to express a few very common things, and those disagreements are where 3.0 specs break when upgraded naively.
The full spec text for both versions lives at spec.openapis.org; this guide covers the changes that actually show up in real-world specs, not the complete changelog.
Breaking: nullable Is Gone
In OpenAPI 3.0, JSON Schema Draft 4 had no way to say "this value can be a string or null," because type could only hold a single string. OpenAPI patched around that gap with a vendor keyword:
# OpenAPI 3.0 tag: type: string nullable: true
JSON Schema Draft 6 (and everything after it, including 2020-12) fixed the actual gap: type can be an array of type names. OpenAPI 3.1 adopts that directly and drops nullable entirely — it is not deprecated, it is not a recognized keyword in a 3.1 document. The equivalent is:
# OpenAPI 3.1 tag: type: [string, "null"]
A spec that carries nullable: true forward into a document declaring openapi: 3.1.0 doesn't error in most tooling — it just gets silently ignored, which is worse than an error, since the field quietly stops meaning anything. Tooling that validates strictly against the 2020-12 meta-schema will flag it as an unrecognized keyword.
Breaking: exclusiveMinimum/exclusiveMaximum Change Type
This is the change most likely to produce a validation error rather than a silent no-op, because the keyword doesn't disappear — it changes type, from boolean to number, and a boolean where a number is expected is a hard validation failure.
In Draft 4 (OpenAPI 3.0), exclusiveMinimum is a boolean modifier that changes how minimum is interpreted:
# OpenAPI 3.0 — "must be greater than 0" price: type: number minimum: 0 exclusiveMinimum: true
In Draft 6+ (OpenAPI 3.1), exclusiveMinimum stands alone and carries the boundary value itself:
# OpenAPI 3.1 — same constraint price: type: number exclusiveMinimum: 0
The same applies to exclusiveMaximum. Converting means deleting the old minimum/maximum key and moving its value onto exclusiveMinimum/exclusiveMaximum when the boolean was true — if it was false, minimum/maximum stays as-is and the (now-invalid) boolean keyword is simply dropped.
Additive: Top-Level webhooks
OpenAPI 3.0 could only describe requests your API receives. Plenty of real APIs also send requests — a payment provider calling your endpoint when a charge settles, for instance — and 3.0 had no first-class way to document that shape of interaction outside of the operation-scoped callbacks field, which ties a callback to one specific operation and requires a runtime expression to compute the target URL.
OpenAPI 3.1 adds a top-level webhooks map, a sibling of paths, using the same Path Item Object shape. Unlike callbacks, a webhook isn't tied to any particular operation or a dynamically computed URL — it documents a named, standalone incoming request the API may issue to a registered subscriber:
openapi: 3.1.0
webhooks:
newPetAdded:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
responses:
'200':
description: Webhook receivedA direct consequence: in 3.1, paths is no longer required at the document root. A spec that describes only webhooks — a pure event-driven integration with no inbound endpoints at all — is now a structurally valid OpenAPI document.
Additive: info.summary and license.identifier
Two small but frequently useful additions to the info object. info.summary is a short, one-line synopsis distinct from the (often much longer) Markdown-formatted info.description — tooling that renders a compact API catalog can use summary without truncating a paragraph of prose.
license.identifier lets you declare a license using an SPDX license expression (MIT, Apache-2.0) instead of, or as well as, a URL:
info:
title: Petstore API
version: 1.0.0
summary: A sample API for managing pets in a store
license:
name: Apache 2.0
identifier: Apache-2.0license.url and license.identifier are mutually exclusive — a document that sets both is invalid. Machine-readable licensing matters more than it sounds: package registries and SDK generators that publish from a spec need an unambiguous, parseable license string, not a URL they'd have to fetch and scrape.
Additive: examples (Plural) Over example
JSON Schema 2020-12's native example-carrying keyword is examples, an array. OpenAPI 3.0's Schema Object only had the OpenAPI-specific singular example keyword. OpenAPI 3.1 deprecates the schema-level singular example in favor of the standard examples array, so schema authors get one example format that's portable to any JSON Schema tool, not just OpenAPI-aware ones:
# OpenAPI 3.0 status: type: string example: "shipped" # OpenAPI 3.1 (preferred) status: type: string examples: ["shipped"]
This is scoped to the Schema Object specifically — the Parameter Object and Media Type Object's own example/examples fields (which describe example values for a whole request/response, not a schema fragment) are unaffected and work the same in both versions.
Migration Checklist
- Bump
openapi:to a3.1.xvalue. - Replace every
nullable: truewith atypearray including"null". - Convert boolean
exclusiveMinimum/exclusiveMaximumpairs into standalone numeric values. - Consider whether any operation-scoped
callbackswould be better modeled as top-levelwebhooks. - Move schema-level
exampletoexampleswhere practical (not required —exampleis deprecated, not removed). - Re-run validation — see our common validation errors guide for the mistakes that show up most in freshly migrated specs.
Most of these changes are mechanical once you know to look for them. Paste a spec — YAML or JSON — into our OpenAPI / Swagger Viewer and its validator will flag version-specific issues directly. If your spec started life as YAML and you need to convert it to JSON (or check it's well-formed before pasting it in), our YAML/TOML Validator & Converter handles the round trip; for schema fragments you're editing directly, the JSON Formatter & Validator catches syntax errors before they reach the OpenAPI-specific checks.
Further Reading
- OpenAPI Specification v3.1.0 (spec.openapis.org)
The authoritative OpenAPI 3.1.0 specification text, including the full Schema Object definition.
- OpenAPI Specification v3.0.3 (spec.openapis.org)
The OpenAPI 3.0.3 specification, for direct comparison against 3.1.
- JSON Schema 2020-12 specification
The JSON Schema draft that OpenAPI 3.1 Schema Objects are fully aligned with.
- OpenAPI Initiative blog — "OpenAPI 3.1.0 has been Released"
The OpenAPI Initiative’s own announcement and rationale for the 3.1 changes.