oapi-codegen v2.7.0 Release Notes

Release Date: 2026-05-01 // about 1 month ago
  • ๐Ÿ›  Many improvements and even more bug fixes

    ๐Ÿš€ This v2.7.0 release of oapi-codegen contains quite a bit of internal refactoring, focused on our most historically fragile code paths, which relate to the aggregate types (allOf/anyOf/oneOf), $ref to external specs, enums, and the spec traversal logic missing quite a few leaf nodes where models should have been generated, but were skipped.

    The biggest changes are explicitly described in the sections below, and the full list of commits is at the bottom.

    โšก๏ธ Thank you to all contributors, we've been going through all past PR's and updating them and merging where we can, and thanks to all our users for reporting issues that you hit.

    ๐Ÿš€ I've (@mromaszewicz) used a lot of LLM help here to scrub through old issues and do some deep internal refactoring to address common problem areas. I intend to continue doing this, since the conditional generation logic is getting quite complicated. When I originally released oapi-codegen, the use case was much simpler, all the models were under #/components/schemas, and all the references to them were in the requests, responses, etc. I never imagine how many things would be external references or unions, and how many complex OpenAPI specifications people would be generating code for. The initial design was never flexible enough to handle that, so ongoing bug fixes are getting increasingly complex due to edge cases. This version has a lot of internal changes you won't see as a user, but the way we handle type generation internally is unifying lots of copy/paste re-implementations into reusable code for consistency. Most of these changes can be done transparently, but some can't, so, onto the changes:

    Code generation changes which might require some changes on your end

    ๐Ÿš€ This release contains three changes, all very narrow in scope, which will require some manual adjustment of your own code. We've decided that these are small enough and uncommon enough not to require opt-in, which causes internal complexity. It's always a judgment call with these. If we got it wrong, we're happy to revisit it in a maintenance release.

    ๐Ÿ“ฆ Strict-server external response refs require strict-server generation in both packages (#2357)

    If your strict-server spec uses an external $ref to a components/responses/... defined in another spec, that other
    spec must also be generated with strict-server: true. Add it to the source spec's config and regenerate:

    # config for the spec being $ref'dgenerate:models:truestrict-server:true # now required when imported by a strict-server spec
    

    ๐Ÿ“ฆ This restores the v2.0.0 behavior that lets you cast response models across package boundaries โ€” the standard pattern
    for sharing error models (e.g. a common 400) across services. PR #1387 had silently changed the embedded type from N400JSONResponse to the bare externalRef0.N400, so the local and external response structs no longer had
    matching types and casts stopped compiling.

    Many more anonymous inner schemas are now hoisted into top level schemas

    Inline oneOf, anyOf, and additionalProperties schemas embedded directly under an operation's request or response
    body now flow through the same boilerplate-emission pipeline as components/schemas, so they get the
    As* / From* / Merge* accessor methods they were previously missing. As part of that change, two older naming patterns
    are replaced with one pattern, shared with all components:

    GetPets_200_Data_Item โ†’ GetPets200JSONResponseBody_Data_Item
    GetPets200JSONResponse_Data_Item โ†’ GetPets200JSONResponseBody_Data_Item
    

    In practice, we think this shouldn't break anyone, because this change addresses a bug which produced pointless types
    with no benefit, and you never interact with these directly, but rather you'd call an accessor on a field of a model.

    Strict middleware typedefs are now inlined (#2271)

    StrictHandlerFunc and StrictMiddlewareFunc in generated strict-server code are now inline type definitions instead
    ๐Ÿ“ฆ of aliases to github.com/oapi-codegen/runtime/strictmiddleware/<framework>. Generated servers no longer import that package.

    Before (Echo example):

    importstrictecho"github.com/oapi-codegen/runtime/strictmiddleware/echo"typeStrictHandlerFunc=strictecho.StrictEchoHandlerFunctypeStrictMiddlewareFunc=strictecho.StrictEchoMiddlewareFunc
    

    After:

    typeStrictHandlerFuncfunc(ctxecho.Context,requestany) (any,error)typeStrictMiddlewareFuncfunc(fStrictHandlerFunc,operationIDstring)StrictHandlerFunc
    

    ๐Ÿ“ฆ If your code referenced the per-framework names directly โ€” strictecho.StrictEchoHandlerFunc, strictgin.StrictGinHandlerFunc, strictnethttp.StrictHTTPHandlerFunc, strictiris.StrictIrisHandlerFunc, strictecho5.StrictEcho5HandlerFunc โ€” switch to the local StrictHandlerFunc / StrictMiddlewareFunc exposed by the generated server package, or import runtime/strictmiddleware/<framework> yourself if you really want those names. The underlying signatures are unchanged, so any value satisfying the old type still satisfies the new one.

    ๐ŸŽ‰ Notable changes

    Go 1.24 required (#2264)

    ๐Ÿš€ oapi-codegen itself now requires Go 1.24.4+ to build and run. The toolchain in your project's go.mod (the one used to invoke the codegen) must be โ‰ฅ 1.24.4. The code generated will still likely work on older versions. We had to update to Go 1.24 in order to update some dependencies to address vulnerabilities. Go 1.24 is no longer supported, so our next release will update to Go 1.25,
    ๐Ÿ‘ and the plan is to stay on supported Go versions. I'm not sure if 1.25 will come in v2.8.0 or v2.7.1 yet, but it's imminent. We
    โœ… have a number of submodules in this repo which exist only to test Go 1.25 routers in a 1.24 module, and it allows us to
    simplify.

    ๐Ÿ— Unfortunately, some of our transitive dependencies result in a broken build, by default, so you might have to pin these
    ๐Ÿ“ฆ packages to specific versions:

    • github.com/speakeasy-api/jsonpath v0.6.3
    • ๐Ÿ‘€ github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 (See #2015 for some discusion)

    Multi-pass type name resolution (#2213)

    Set output-options.resolve-type-name-collisions: true to make the codegen detect identifier collisions across schemas, parameters, request bodies, response components, and operation-derived types โ€” and resolve them deterministically by suffixing the loser. Specs that previously failed to generate because two definitions wanted the same Go name now succeed.

    Trivial example. With this spec:

    components:schemas:Status:type:stringenum:[active, archived]parameters:Status:name:statusin:queryschema:type:string
    

    output-options.resolve-type-name-collisions: true produces:

    typeStatusstring// from components.schemas.StatusconstStatusActiveStatus="active"constStatusArchivedStatus="archived"typeStatusParameter=string// from components.parameters.Status
    

    Collision resolution is opt-in. Generated identifier names depend on the current set of collisions in the spec;
    โž• adding a new schema or parameter later that collides with an existing one will rename the existing one to break the new collision.
    That can silently break user code that imports the previously-stable name as the spec drifts. However, despite the drift,
    more specs can now correctly generate boilerplate.

    Parameter binding matrix (#2307)

    ๐Ÿ’… The OpenAPI parameter style ร— explode ร— type matrix is now fully supported and round-trips consistently across
    every server backend. Path/query/header/cookie parameters across primitive, array, and object types โ€” including
    ๐Ÿ’… style: form / spaceDelimited / pipeDelimited / deepObject ร— explode: true/false, and style: simple for headers โ€”
    โœ… generate the same binding logic on every server, and the client-side encoding is symmetric. The internal parameter test suite (internal/test/parameters/) now exercises every combination through a server round-trip per backend.

    ๐Ÿ’… If you previously hit an unsupported style error, or saw a parameter serialization work under one backend but not another,
    regenerate and the issue should be gone.

    You will need to use version v1.4.0 or higher of github.com/oapi-codegen/runtime.

    Optional / nullable response headers (#2301)

    For strict-server responses, optional and nullable headers now generate as pointer fields (or nullable.Nullable[T]
    when the nullable-types output option is set). The generated server only calls w.Header().Set(...) when the field
    is non-nil, so callers can omit optional headers cleanly.

    Spec:

    responses:'200':headers:X-Required:{ required: true, schema: { type: string } }X-Optional:{ schema: { type: string } }
    

    Before:

    typeGetFoo200ResponseHeadersstruct{XRequiredstringXOptionalstring// always emitted, even when empty}
    

    After:

    typeGetFoo200ResponseHeadersstruct{XRequiredstringXOptional\*string// nil โ†’ header not sent}
    

    โช To opt out of this change, set compatibility.headers-implicitly-required: true to restore the previous always-required behavior. This change breaks enough code that we flagified it.

    ๐Ÿš€ New features

    ๐Ÿ‘ Echo v5 server support ([#2188](https://github.com/oapi-codegen/oapi-cod...


Previous changes from v2.6.0

  • For those that aren't aware, 7 years ago to the day, oapi-codegen was born!

    (Well, technically it's tonight at midnight UTC, but who's splitting hairs?)

    ๐Ÿš€ There's nothing too special planned for today, but we thought it'd be the perfect time to cut a slice of cake a release!

    ๐ŸŽ‰ Notable changes

    ๐Ÿ†• New generated code requires oapi-codegen/runtime v1.2.0+

    ๐Ÿš€ As part of #2256, github.com/oapi-codegen/runtime v1.2.0 is needed alongside github.com/oapi-codegen/oapi-codegen, for new generated code.

    This is providing a more future-proofed means to bind parameters.

    ๐Ÿš€ See the release notes for the runtime package, and #2256 for more information.

    oapi-codegen was part of the GitHub Secure Open Source Fund

    oapi-codegen was one of the projects taking part in the third GitHub Secure Open Source Fund session.

    We've written up more about what we've learned, and have some more things to share with you over the coming months about lessons we've learned and improvements we've taken that we can share.

    ๐Ÿ”’ We were pretty chuffed to be selected, and it's already helped improve our security posture as a project, which is also very important for the wider ecosystem!

    ๐Ÿš€ go directive bump in next release

    Long-time users will be aware that we work very hard to try and keep our requirement for Go source compatibility, through the go directive, especially as we recommend folks use oapi-codegen as a source-tracked dependency.

    ๐Ÿ‘€ For more details about this, see our Support Model docs.

    ๐Ÿš€ In the next minor release, we'll be setting our minimum go directive to Go 1.24 (End-of-Life on 2026-02-11), as it's required for a number of dependencies of ours to be updated any higher, and a change to the module import path for Speakeasy's OpenAPI Overlay library requires us fix this centrally for our users to be able to continue updating their libraries.

    Note

    Nothing is changing as part of v2.6.0, this is a pre-announcement for v2.7.0.

    Behind the scenes cleanup

    ๐Ÿ›  There's also been some work behind-the-scenes to try and clean up outstanding issues (of which we know there are many!) that have been fixed, as well as Marcin's work on trying to do some more significant rework of the internals with help from Claude.

    There's still, as ever, work to go with this - as we've mentioned before, sponsoring our work would be greatly appreciated, so we can continue to put in the work, considering this is a widely used and depended on project.

    ๐Ÿš€ New features and improvements

    • ๐Ÿ‘ feat: Pass schema type/formats to runtime v1.2.0 to allow better parameter serialization (#2256) @mromaszewicz
    • feat: add Valid() method to generated enum types (#2227) @mromaszewicz
    • ๐Ÿ‘Œ Support nullable slice elements and map values (#2185) @iamtakingiteasy
    • ๐Ÿ”ง feat: add configurable type mapping for OpenAPI primitive types (#2223) @mromaszewicz
    • ๐Ÿ‘Œ Support unions with multiple mappings pointing to a single underlying type (#2071) @tobio
    • ๐Ÿ“ฆ feat: add support for custom package alias for external ref imports (#2211) @InventivetalentDev
    • feat(output-options): add resolve-type-name-collisions to avoid name collisions (#200) @mgurevin
    • โšก๏ธ Adopt fiber middleware template for updated GetReqHeaders() method signature (#1419) @getBolted

    ๐Ÿ›  ๐Ÿ› Bug fixes

    • ๐Ÿ›  fix: qualify external ref schema types in default response codes (#2241) @mromaszewicz
    • ๐Ÿ›  fix: add omitempty to optional nullable fields (#2221) @mromaszewicz
    • ๐Ÿ›  fix(codegen): generate nullable.Nullable in arrays (#2242) @jamietanna
    • ๐Ÿ›  fix: support x-oapi-codegen-extra-tags on parameter schemas (#2232) (#2235) @mromaszewicz
    • ๐Ÿ›  fix(templates/client): correctly nil check query parameters (#2237) @jamietanna
    • ๐Ÿ›  fix(server-urls): restore generation of constants (#2239) @jamietanna
    • ๐Ÿ›  fix(server-urls): use URL in GoDoc if description is empty (#2226) @jamietanna
    • ๐Ÿ›  fix: set indentation to 2 when marshalling spec for overlay (#2172) @wndhydrnt
    • ๐Ÿ›  fix: handle optional request bodies in strict server mode (#2222) @mromaszewicz
    • ๐Ÿ›  fix(strict-server): generate correct type for $ref text responses (#2225) @mromaszewicz
    • ๐Ÿ›  Fix schema gathering oversight (#2219) @mromaszewicz
    • ๐Ÿ›  fix: handle duplicate path parameters in OpenAPI specs (#2220) @mromaszewicz
    • ๐Ÿ›  fix: escape quoted media type directives (#2217) @brahmlower
    • ๐Ÿ›  Fix Iris strict server for no content case (#1411) @ShouheiNishi
    • ๐Ÿ›  Fixes type collision for enum values that start with _ (underscore) (#1438) @ula

    ๐Ÿ“š ๐Ÿ“ Documentation updates

    ๐Ÿšง ๐Ÿ‘ป Maintenance

    • โšก๏ธ chore(renovate): add module path to security updates + override test-only dependencies' label (#2249) @jamietanna
    • ๐Ÿ”ง Configure Greptile code review (#2236) @mromaszewicz
    • ๐Ÿ’… style(gofix): Apply go fix (#2229) @gaiaz-iusipov
    • ๐Ÿ‘• Run golangci-lint on a supported Go version (#2215) @mromaszewicz
    • ๐Ÿ”จ refactor(internal): move Fiber tests into their own modules (#2212) @mromaszewicz
    • ๐Ÿ— build: use a re-usable, single, workflow for running CI (#2205) @jamietanna
    • ๐Ÿ›  fix(renovate): only run make tidy after Go module updates (#2159) @jamietanna
    • โšก๏ธ chore(renovate): run make tidy after dependency updates to go.mod (#2150) @jamietanna

    โšก๏ธ ๐Ÿ“ฆ Dependency updates

    8 changes

    • โšก๏ธ chore(deps): update module github.com/golangci/golangci-lint to v2.10.1 (makefile) (#2153) @renovate[bot]
    • โšก๏ธ chore(deps): update github/codeql-action action to v4.32.4 (.github/workflows) (#2157) @renovate[bot]
    • โšก๏ธ chore(deps): update actions/setup-go action to v6.3.0 (.github/workflows) (#2164) @renovate[bot]
    • โšก๏ธ chore(deps): update actions/checkout action to v6 (.github/workflows) - autoclosed (#2165) @renovate[bot]
    • ๐Ÿš€ chore(deps): update release-drafter/release-drafter action to v6.2.0 (.github/workflows) (#2253) @renovate[bot]
    • โšก๏ธ chore(deps): update actions/upload-artifact action to v7 (.github/workflows) (#2254) @renovate[bot]
    • โšก๏ธ chore(deps): update dessant/label-actions action to v5 (.github/workflows) (#2255) @renovate[bot]
    • ๐Ÿš€ chore(deps): update release-drafter/release-drafter action to v6.1.0 (.github/workflows) (#2132) @renovate[bot]

    Sponsors

    ๐Ÿš€ We would like to thank our sponsors for their support during this release.

    Cybozu logo