Engineering

OpenAPI codegen: what it does, which tool to pick, what to avoid

"OpenAPI codegen" describes the whole category of tools that turn an OpenAPI 3.x spec into SDK source code. It's a crowded field — open-source generators, commercial SDK-as-a-service tools, framework-specific plugins. This is the short version of what each one does, when to use which, and the patterns that decide whether generated code is good or just generated.

The category, mapped

Open-source code generators

  • OpenAPI Generator — the most-used. 50+ language targets. Apache 2.0. Run via CLI / Docker / Maven.
  • Swagger Codegen — OpenAPI Generator's parent project (the latter is a 2018 community fork). Still maintained; smaller community.
  • oapi-codegen — Go-specific generator. Excellent for Go-first stacks.
  • openapi-typescript-codegen — TypeScript-focused. Lighter than OpenAPI Generator for TS-only projects.
  • Quicktype — JSON Schema → type definitions, broader than OpenAPI.

Commercial / SaaS generators

  • Bloom — TypeScript + Python today. Docs + SDKs + migration report in one workflow.
  • Stainless — broad language coverage, mature MCP target, recently joined Anthropic.
  • Fern — docs + SDKs together. OpenAPI-native.
  • Speakeasy — OpenAPI-native, strong on per-language idiom.
  • APIMatic — portal + SDKs, longer-running player.

Framework-specific generators

If you use NestJS, FastAPI, Spring Boot, Django REST, Rails Grape, etc., your framework probably has a generator built into its ecosystem. Often the best fit if you want a code-first workflow (annotations on routes drive the spec).

What codegen actually produces

A generated SDK includes (at minimum):

  • A client class with a constructor that accepts auth and base URL.
  • Resource modules (one per OpenAPI tag) containing methods that wrap each operation.
  • Typed schemas for every request and response shape.
  • Error classes for the standard HTTP statuses.
  • A README with an example.

The quality differential between generators is in the details:

  • Discriminated unions for oneOf / anyOf schemas (clean) vs wide objects with all optional fields (sloppy).
  • Idiomatic null handling (Optional[X] in Python, X | null in TS 3.1+) vs verbose Any | None workarounds.
  • Sync + async clients in Python (modern) vs sync-only.
  • Examples that use real values from your spec vs { "name": "string" } placeholders.
  • Method names in language convention (send_message in Python, sendMessage in TS) vs whatever the operation ID was.

How to pick a generator

Three real questions, in order:

1. What does your customer base look like?

  • Mass-market or self-serve: customers will use the SDK without much hand-holding. Lean toward a generator with clean output and good defaults. Commercial generators usually win here because they tune per-language idiom carefully.
  • Enterprise: customers have integration engineers who will tolerate rougher edges. Open-source generators are fine, especially if you can fork the templates.

2. What languages do you need?

  • TS + Python only: most commercial generators cover this. Bloom, Stainless, Fern, Speakeasy all work.
  • Go / Java / Ruby / .NET / etc.: Stainless or OpenAPI Generator. Fewer options at the top of the quality curve.
  • One specific language deeply (e.g. Go-first): a language-specific generator (oapi-codegen) often beats a polyglot one.

3. Do you need docs + release workflow, or just code?

  • Just code: open-source generators. Wire docs + release yourself.
  • Workflow: Bloom, Fern, or Stainless. The value-add is everything around the code generation.

Patterns that produce bad generated code

If you're unhappy with what your generator emits, the issue is usually in the spec, not the generator. Specifically:

1. Inline duplicate schemas

When the same shape appears inline across multiple operations, generators create Schema1, Schema2, etc. — non-interchangeable types. Move shared shapes to components.schemas and $ref them. See the OpenAPI best practices post.

2. Missing operationId

Generators fall back to URL+method-based names (messagesPostV2) which are ugly. Always set operationId.

3. Wide unions instead of discriminated ones

oneOf without discriminator produces unions the language can't narrow. Always pair oneOf with discriminator for sum types.

4. Polymorphism via allOf with overlapping properties

Some specs model inheritance as allOf: [BaseSchema, { extra fields }]. Generators handle this inconsistently. If you want subtype polymorphism, use oneOf + discriminator.

5. Examples missing or generic

Without examples, generated READMEs say things like { "name": "string" }. Customer impressions of your SDK quality are formed in the first 30 seconds of reading the README. Add operation-level examples.

How Bloom thinks about generated quality

Bloom-generated SDKs target a specific quality bar:

  • Methods named in language convention, derived from operationId.
  • Examples come from operation-level examples blocks in the spec.
  • Errors are typed subclasses of a per-SDK APIError.
  • TypeScript output uses discriminated unions where the spec uses oneOf + discriminator.
  • Python output uses Optional[X] (3.10+) for nullable schemas.
  • Auth flows from securitySchemes to constructor options + env var fallbacks.

If you want to compare to your current generator, start a Bloom report and run the compatibility report — it diffs both SDKs against a stable reference.

Migration paths

If you're already on:

  • OpenAPI Generator → most direct migration. The spec is the same; the generator changes. Use the compat report to verify nothing breaks.
  • Hand-rolled SDKs → start with the spec. Run codegen against it; diff the output against your hand-written code; merge what matters.
  • Stainless → see the Bloom vs Stainless page for the parity table and migration path.
  • A combo of multiple tools → the spec is the single source of truth. Codegen is the right factoring; hand-maintained per-language code drifts.

What to do this week

  • If you're choosing for a new project, pick based on the three questions above. Don't pick on hype.
  • If you're already on a generator and unhappy, check whether the issue is the spec first.
  • If you're maintaining hand-written SDKs alongside your spec, that's a migration opportunity.

OpenAPI codegen is mature enough that the right tool is usually obvious once you know what you're optimizing for. The mistake is treating it as a commodity and picking whichever tool the team's tutorial happened to mention.