从 OpenAPI 3.0 到 3.1:一次升级验证记录
目录
📦 本文基于的完整项目源码:https://github.com/meirongdev/shop
🏷️ 当前文章对应的代码版本:
openapi_3_1_upgrade
背景#
我们的电商平台自启动以来,一直使用 OpenAPI 3.0.x 作为 API 文档规范。近期在整理 SpringDoc 2.8.9 和 JSON Schema 2020-12 工具链时,我顺手验证了一次 OpenAPI 3.1 升级的可行性,最后决定先在项目里试着切过去。
就这次实践看,升级过程主要是一行配置,没有涉及 Java 代码改动。下面更像是一份升级笔记:记录我为什么做这件事、哪些收益比较直接,以及哪些兼容性边界要先确认。
OpenAPI 3.1 的核心变化#
OpenAPI 3.1 于 2021 年底发布(官方发布公告),虽然看起来只是小版本号 +1,但它底层做了一个根本性的改变:全面对齐 JSON Schema Draft 2020-12(JSON Schema 2020-12 规范)。
这不只是"换了一个标准",更像是把 OpenAPI Schema 往 JSON Schema 工具链上靠近了一步。
变化一:nullable 语义重构#
OpenAPI 3.0 的写法:
{
"type": "string",
"nullable": true
}
这是一个 OpenAPI 自定义扩展字段。JSON Schema 标准中不存在 nullable,这意味着:
- 标准 JSON Schema 校验器不认识它
- 代码生成器需要专门处理 OpenAPI 特有的逻辑
- 与 JSON Schema 生态工具链不互通
OpenAPI 3.1 的写法:
{
"type": ["string", "null"]
}
这是 JSON Schema Draft 2020-12 的原生语法。type 字段接受类型数组,null 成为一个合法的类型值。
实际影响:
// 项目中的 DTO 注解(代码不变)
@Schema(description = "用户昵称,可选填", nullable = true)
private String nickname;
| 维度 | OpenAPI 3.0 输出 | OpenAPI 3.1 输出 |
|---|---|---|
| 生成的 JSON | {"type":"string","nullable":true} |
{"type":["string","null"]} |
| JSON Schema 校验器 | ❌ 不识别 | ✅ 原生支持 |
| 代码生成器 | 需要 OpenAPI 专用逻辑 | 使用标准 JSON Schema 逻辑 |
变化二:$ref 与同级字段共存#
OpenAPI 3.0 中,$ref 不能与任何其他字段同时出现(参见 OpenAPI 3.0 规范)。如果需要给引用加描述,只能用 allOf 嵌套:
{
"allOf": [
{ "$ref": "#/components/schemas/User" }
],
"description": "The user who created this order"
}
OpenAPI 3.1 对齐了 JSON Schema 的行为,$ref 可以与 description、deprecated、example 等字段直接共存(参见 OpenAPI 3.1 规范 - Reference Object):
{
"$ref": "#/components/schemas/User",
"description": "The user who created this order"
}
这消除了长期以来被开发者诟病的 allOf 包装模式(相关 GitHub 讨论:OAI/OpenAPI-Specification#1219)。
变化三:examples 多值支持#
OpenAPI 3.0 中,每个参数/响应体只能有一个 example(参见 OpenAPI 3.0 规范 - Example Object):
{
"schema": { "type": "string" },
"example": "buyer.demo"
}
OpenAPI 3.1 引入了 examples Map,可以为同一个字段提供多个示例(参见 OpenAPI 3.1 规范 - Media Type Object):
{
"schema": { "type": "string" },
"examples": {
"buyer": { "value": "buyer.demo" },
"seller": { "value": "seller.demo" },
"vip": { "value": "buyer.vip" }
}
}
这对 API 文档的消费者非常有价值——一个接口可以展示不同角色、不同场景下的多种示例值。
变化四:Webhooks 原生支持#
OpenAPI 3.0 没有描述 Webhook / 回调的标准方式。callbacks 字段只能描述基于请求-响应模式的回调(参见 OpenAPI 3.0 规范 - Callback Object),不适合事件驱动的推送场景。
OpenAPI 3.1 在顶层引入了 webhooks 字段(参见 OpenAPI 3.1 规范 - Webhooks):
openapi: 3.1.0
info:
title: Order Service API
version: v1
webhooks:
orderCreated:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/OrderEvent'
responses:
'200':
description: Webhook received
这更适合描述我们平台里偏事件驱动的回调或推送场景(如 buyer.registered.v1、order.events.v1)。
变化五:deprecated 字段规范化#
OpenAPI 3.1 允许在 Security Scheme 上标记 deprecated(参见 OpenAPI 3.1 规范 - Security Scheme Object),这对于 OAuth 迁移、JWT 算法迁移等场景很有价值:
components:
securitySchemes:
oldJwtAuth:
type: http
scheme: bearer
bearerFormat: JWT-HS256
deprecated: true # ← 3.1 新增
newJwtAuth:
type: http
scheme: bearer
bearerFormat: JWT-RS256
变化六:exclusiveMinimum / exclusiveMaximum 语义变更#
OpenAPI 3.0:
{ "minimum": 0, "exclusiveMinimum": true }
OpenAPI 3.1(对齐 JSON Schema Draft 2020-12,参见 JSON Schema 规范 - exclusiveMinimum):
{ "exclusiveMinimum": 0 }
exclusiveMinimum 和 exclusiveMaximum 从布尔标志变成了数值本身。这意味着 exclusiveMinimum: 0 表示"大于 0",而不是"大于等于 0"。
为什么现在升级#
1. 工具链支持已经比较完整#
| 工具 | OpenAPI 3.1 支持状态 |
|---|---|
| SpringDoc 2.8.9 | ✅ 原生支持,一行配置即可 |
| Swagger UI 5.x | ✅ 完整支持 |
| JSON Schema 校验器(Ajv, jsonschema) | ✅ Draft 2020-12 原生 |
| Postman | ✅ 支持 |
| Redoc | ✅ 支持 |
| OpenAPI Generator 7.x | ✅ 支持 |
2. 与 JSON Schema 生态互通#
这是这次升级里我最看重的一点。过去 OpenAPI 的 Schema 更像是一个"接近 JSON Schema,但仍有差异"的子集,导致:
- 不能直接拿很多 JSON Schema 校验器来验证请求/响应
- 代码生成器往往要维护 OpenAPI 专用的类型映射
- 和 JSON Schema 社区里的一些工具、库、实践之间总有一层转换成本
升级到 3.1 后,OpenAPI Schema 采用了 JSON Schema Draft 2020-12 的语义,大多数现代 JSON Schema 工具链能更顺畅地接入。不过如果团队里还有旧版解析器或生成器,兼容性仍然要逐个确认。
3. 为 OpenAPI 3.2 铺路#
OpenAPI 3.2(2025 年 9 月发布)在 3.1 的基础上增加了 Streaming、QUERY 方法、结构化 Tags 等特性。对大多数团队来说,如果后面想继续跟进 3.2,通常也会先把 3.1 这一步走稳。
升级过程#
改动量:每个服务仅一行配置#
# application.yml
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
version: openapi_3_1 # ← 新增这一行
至少在这次升级里,Java 代码没有改动。 @Schema、@Operation、@ApiResponse 等注解可以继续沿用,SpringDoc 在序列化时输出 3.1 格式的 JSON。
我们平台的实际操作#
平台共有 15 个微服务(1 Gateway + 2 BFF + 12 Domain Service),每个服务的 application.yml 添加一行 version: openapi_3_1:
springdoc:
api-docs:
enabled: ${SPRINGDOC_ENABLED:true}
path: /v3/api-docs
+ version: openapi_3_1
按这次提交统计:15 行配置变更,0 行 Java 代码变更。
升级后的验证#
升级后,访问任意服务的 /v3/api-docs 端点:
curl http://127.0.0.1:18080/v3/api-docs/buyer | jq '.openapi'
输出应为:
"3.1.0"
同时在 Swagger UI 中查看 nullable 字段的 Schema:
// 3.0 格式(升级前)
{ "type": "string", "nullable": true }
// 3.1 格式(升级后)
{ "type": ["string", "null"] }
注意事项#
下游工具兼容性#
如果团队中有下游系统(如第三方 API 消费者、旧版代码生成器)依赖你的 OpenAPI spec,需要确认它们支持 3.1。主要关注点:
nullable→type: ["...", "null"]:旧版解析器可能不识别数组形式的typeexclusiveMinimum语义变化:从布尔值变为数值$ref同级字段:旧版解析器可能忽略$ref旁的description
对于 demo / 内部项目,这些通常不是问题。对于对外暴露的 API,建议通过版本号灰度或提供双版本 spec(3.0 和 3.1 同时可用)。
@Schema(nullable = true) 注解#
Java 代码中的 @Schema(nullable = true) 注解不需要修改。SpringDoc 在 3.1 模式下会自动将其转换为 type: ["string", "null"] 格式。注解是 Java 层的抽象,与输出格式无关。
总结#
| 维度 | OpenAPI 3.0 | OpenAPI 3.1 |
|---|---|---|
| Schema 基础 | 自定义 subset | JSON Schema Draft 2020-12 |
nullable |
自定义字段 | type 数组含 "null" |
$ref 同级字段 |
不支持 | 支持(description、deprecated 等) |
examples |
单值 | Map 多值 |
| Webhooks | 无 | 原生 webhooks 字段 |
| 与 JSON Schema 生态 | 需要额外适配 | 对接更顺畅 |
| 升级成本 | — | 一行配置,零代码变更 |
按我这次升级验证的感受,OpenAPI 3.1 最直接的价值,不是多了几个字段,而是和 JSON Schema 生态的对接更顺了。这让 API 文档不再只是给 Swagger UI 看的一份描述,也更容易接入校验器、代码生成器、表单生成器或 Mock 服务。
如果你的项目还在用 OpenAPI 3.0,且下游工具已经确认支持 3.1,我觉得这条升级路径至少值得纳入评估。
技术栈版本:Java 25 · Spring Boot 3.5.11 · Spring Cloud 2025.0.1 · SpringDoc 2.8.9 · OpenAPI 3.1.0