Table of Contents

    Book an Appointment

    How did a .NET 10 OpenAPI migration break our client SDKs?

    While working on a massive API modernization effort for an enterprise logistics platform, we upgraded the core microservices architecture to .NET 10. As part of this transition, we decided to drop our dependency on Swashbuckle and adopt the built-in .NET OpenAPI generator. The goal was to streamline our dependencies and utilize the native framework capabilities.

    Everything appeared to compile and run perfectly in our backend environments. However, the issue surfaced when our frontend teams and third-party logistics partners attempted to update their auto-generated API clients. They reported immediate breaking changes. Operations that relied on optional filters—specifically nullable enum properties like ShipmentStatus? Status—were suddenly generating completely disconnected wrapper classes, such as ShipmentStatus2, instead of mapping to the original enum type.

    In a production system heavily dependent on API contracts, a broken client generator halts parallel development. We realized that the new .NET 10 OpenAPI implementation handles schema generation for nullable types very differently than Swashbuckle did. This challenge inspired this article, providing a clear path to resolve OpenAPI 3.1 enum generation issues so other engineering teams can avoid breaking downstream consumers.

    Why do nullable enums cause issues in modern .NET APIs?

    In our logistics platform, the business use case demands highly flexible search and filter endpoints. For example, querying a list of active shipments often includes optional parameters. In C#, this is elegantly handled using nullable enums, such as public DeliveryState? State { get; set; }.

    When downstream consumers—whether they are Angular frontends, mobile applications or partner microservices—generate their client SDKs using tools like NSwag or OpenAPI Generator, they expect the contract to map back to a simple nullable enum in their respective languages.

    Under Swashbuckle (which historically defaulted to OpenAPI 3.0 schemas), a nullable enum was represented elegantly. The property simply contained the reference to the enum’s schema alongside a straightforward nullable: true flag. Client generators understood this perfectly. However, the architectural shift in .NET’s native OpenAPI tooling brought strict adherence to the newer OpenAPI 3.1 specification, completely altering how nullability is expressed in the JSON document.

    What caused the client generators to create wrapper classes?

    When we analyzed the generated swagger.json from our .NET 10 services, the root cause became evident. The native OpenAPI integration did not append nullable: true. Instead, it generated a oneOf array.

    For a property like MyEnum? MyProp, the output looked like this:

    "MyProp": {
      "oneOf": [
        { "$ref": "#/components/schemas/MyEnum" },
        { "type": "null" }
      ]
    }
    

    While this is technically the correct representation according to the OpenAPI 3.1 specification (where nullable is deprecated in favor of type arrays or oneOf), the reality of the tooling ecosystem is vastly different. Most popular API client generators struggle to parse this specific structure for enums. When they encounter a oneOf constraint, they interpret it as a polymorphic or composite object. To satisfy the constraint safely, the generator creates a brand-new wrapper class (e.g., MyEnum2) instead of a simple nullable variable.

    What approaches did we consider to fix the OpenAPI enum generation?

    When organizations hire dotnet developers for enterprise modernization, they expect architectural foresight that balances spec compliance with practical usability. We evaluated several approaches to resolve this bottleneck.

    Should we wait for API client generators to update?

    We initially considered logging an issue with the client generator repositories (like NSwag) and waiting for full OpenAPI 3.1 parsing support. However, blocking a critical enterprise release on open-source tooling updates was not a viable business decision.

    Should we force the API to emit OpenAPI 3.0 documents?

    Since the problem stems from OpenAPI 3.1 semantics, we tested downgrading the output document version. By configuring the endpoint map with .WithOpenApiVersion(OpenApiSpecVersion.OpenApi3_0), the framework reverted to older structural rules. While this fixed the enum issue, we wanted to retain the benefits of OpenAPI 3.1 for other parts of our schema, making a global downgrade a heavy-handed tradeoff.

    Should we write a custom schema transformer?

    The most surgical and robust approach was leveraging .NET 10’s extensibility. By writing a custom IOpenApiSchemaTransformer, we could intercept the schema generation pipeline, identify schemas utilizing the oneOf null pattern specifically for enums and rewrite them to the highly compatible nullable: true format. This isolated the fix exactly where it was needed.

    How to implement a schema transformer for nullable enums in .NET 10?

    We proceeded with the schema transformer. The implementation focuses on detecting the specific oneOf pattern generated by the .NET 10 framework and flattening it back into a standard reference with the Nullable property set to true.

    The Schema Transformer Code

    using Microsoft.AspNetCore.OpenApi;
    using Microsoft.OpenApi.Models;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    public sealed class NullableEnumSchemaTransformer : IOpenApiSchemaTransformer
    {
        public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
        {
            // Identify schemas that use oneOf for nullability
            if (schema.OneOf != null && schema.OneOf.Count == 2)
            {
                var nullSchema = schema.OneOf.FirstOrDefault(s => s.Type == "null");
                var refSchema = schema.OneOf.FirstOrDefault(s => s.Reference != null);
                // If it contains a null type and a reference type
                if (nullSchema != null && refSchema != null)
                {
                    // Clear the oneOf constraint
                    schema.OneOf = null;
                    
                    // Restore the direct reference to the enum
                    schema.Reference = refSchema.Reference;
                    
                    // Force the legacy Nullable flag for client generator compatibility
                    schema.Nullable = true; 
                }
            }
            
            return Task.CompletedTask;
        }
    }
    

    Registering the Transformer

    Once the transformer was written, we registered it in our dependency injection container during the OpenAPI setup.

    builder.Services.AddOpenApi(options =>
    {
        // Apply the custom transformer to resolve nullable enum generation
        options.AddSchemaTransformer<NullableEnumSchemaTransformer>();
    });
    

    Validating the Fix

    After deploying this configuration, we reviewed the generated swagger.json. The output for MyEnum? MyProp successfully reverted to:

    "MyProp": {
      "$ref": "#/components/schemas/MyEnum",
      "nullable": true
    }
    

    When the frontend team regenerated their SDKs, the wrapper classes disappeared and the properties accurately reflected the original nullable enum types. This custom middleware demonstrated why companies hire backend developers for system integration who deeply understand framework internals rather than just surface-level syntax.

    What can engineering teams learn from this API migration?

    Migrating enterprise applications requires more than just updating framework versions. Here are the core insights from this experience:

    • Specification vs. Tooling Reality: Just because your framework outputs an updated standard (OpenAPI 3.1) does not mean the broader tooling ecosystem is ready to consume it. Always verify downstream compatibility.
    • Include SDK Generation in CI/CD: Do not wait for frontend or partner teams to report broken contracts. Incorporate client SDK generation into your backend CI/CD pipeline to catch schema regressions instantly.
    • Leverage Extensibility Points: Modern .NET provides powerful hooks like IOpenApiSchemaTransformer. Use them for surgical fixes rather than abandoning new framework features entirely.
    • Avoid Global Downgrades: When faced with a specific structural issue, try to transform the specific node rather than downgrading the entire API specification version.
    • Audit Your Contracts: When organizations hire software developer teams for upgrades, a comprehensive audit of generated contracts before and after the framework bump is critical for seamless delivery.

    How can we help modernize your enterprise APIs?

    Transitioning to .NET 10 introduces powerful new paradigms, but as this OpenAPI schema challenge illustrates, the devil is often in the integration details. A successful modernization strategy anticipates tooling friction, mitigates downstream breakages and leverages advanced framework extensibility.

    If your organization is navigating complex framework upgrades, architecture modernization or API transformations, it is essential to have experienced engineers steering the technical direction. When you hire api developers for scalable architecture from our team, you gain access to delivery-focused professionals who solve root causes. If you are looking to scale your engineering efforts with dedicated remote talent, contact us to discuss your project needs.

    Social Hashtags

    #DotNET10 #OpenAPI #OpenAPI31 #ASPNETCore #DotNETDevelopment #APIDevelopment #APIIntegration #SoftwareDevelopment #BackendDevelopment #Microservices #APIModernization #NSwag #DeveloperTools #EnterpriseSoftware

     

    Frequently Asked Questions

    Success Stories That Inspire

    See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.