Before you upgrade to .NET 11, check that your API clients can read OpenAPI 3.2

I moved a small minimal API to .NET 11 RC2 this week and regenerated its Angular client from the new OpenAPI document. NSwag, the first generator I pointed at it, wrote exactly the same client as before. ng-openapi-gen stopped without writing a file: Unsupported OpenAPI version: 3.2.0.
That's the change in .NET 11. AddOpenApi() now writes an OpenAPI 3.2 document where .NET 10 wrote 3.1, and some tools check that number before anything else. So before the upgrade, run your client generator against the document .NET 11 produces. If anything in the pipeline refuses it, pin 3.1 until it catches up. And if your build writes the document, pin it there too, because the documented one-liner doesn't reach the build.
One line changed in the document
Microsoft lists this as a behavioural change from .NET 11 Preview 6, on the breaking-change page for the new default. I expected a new spec version to shuffle at least a few schemas around. So I built the same API on .NET 10 and on RC2: four endpoints, a nullable property, a string enum and a file upload. Then I diffed the two documents.
2c2
< "openapi": "3.1.1",
---
> "openapi": "3.2.0",
That's all of it. Same schemas, same nullable types, same multipart upload. For an API like this one, a tool that breaks is breaking on the version string, and on nothing new in the content.
Five generators, one document
I fed three documents to five generators you'd plausibly find on a .NET and Angular team: .NET 10's, .NET 11's, and a .NET 11 one pinned back to 3.1. In the table, "same" means the generated code is byte for byte what that generator wrote from the .NET 10 document.
documents: .NET 10 = "3.1.1", .NET 11 = "3.2.0", .NET 11 pinned = "3.1.2"
generator .NET 10 .NET 11 .NET 11 pinned
------------------ -------- ---------------- --------------
NSwag ok, same ok, same ok, same
openapi-typescript ok, same ok, same ok, same
ng-openapi-gen ok, same REFUSED (exit 1) ok, same
orval ok, same ok, same ok, same
Kiota ok, same ok, same ok, same
The full message from ng-openapi-gen 1.1.0, the latest on npm this week, is Unsupported OpenAPI version: 3.2.0. Only OpenAPI 3.0.x and 3.1.x are supported. I went and looked: it's a regular expression on the openapi field that allows 3.0 and 3.1. It refuses before it reads a single path. The other four didn't comment on the version at all.
The pin, and the place it doesn't reach
The breaking-change page gives the fix as one line inside AddOpenApi. Note the namespace: it's Microsoft.OpenApi.
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});
I compiled that in, started the app and fetched /openapi/v1.json. It said 3.1.2. Then I checked the document the build writes, the one Microsoft.Extensions.ApiDescription.Server produces during dotnet build. That one still said 3.2.0, and I spent a few minutes rebuilding before I believed it.
The reason is in the ASP.NET Core source. When nobody tells the build-time tool a version, it passes a hard-coded OpenApi3_2, and the options in Program.cs never come into it. On .NET 10 the same line said OpenApi3_1, so the two defaults agreed and there was nothing to notice. The build has its own setting, in the project file:
<PropertyGroup>
<OpenApiGenerateDocumentsOptions>--openapi-version OpenApi3_1</OpenApiGenerateDocumentsOptions>
</PropertyGroup>
It is documented, in one sentence on Microsoft's main Generate OpenAPI documents page. The breaking-change page only shows the AddOpenApi line, and that's the page you'll have open during an upgrade. With both pins in, ng-openapi-gen took the document and wrote exactly what it wrote from .NET 10.
One small oddity: the pinned document says 3.1.2, where .NET 10 said 3.1.1. The newer Microsoft.OpenApi library writes the newer patch number. None of the five generators cared.
What I'd do on a team that generates its client in CI
I'd pin, in both places, and keep the generator. Swapping ng-openapi-gen for another generator in the same pull request as the framework upgrade means a new client: different names, different types, and a diff across every component that calls the API. That deserves its own review. The pin is two lines, and when the generator catches up you delete both together.
I'd also commit the generated document, if the repo doesn't already. Then the upgrade shows up in code review as a one-line diff, 3.1.1 to 3.2.0, which is exactly the line you want a teammate to stop on. Without it, the first sign is a red CI job on the upgrade branch, or a teammate's local build that suddenly can't generate.
I wouldn't bother auditing a 3.2 document for subtle differences. On this API there weren't any. The time is better spent on the boring version: build on RC2, feed the document to every tool that reads it, from the client generator to a contract diff to Swagger UI, and pin 3.1 in both places for any tool that says no.