Is OpenAPI Repeating WSDL's Mistakes?

Complexity of OpenAPI Versions

OpenAPI is evolving from a practical interface description language into one that attempts to model every corner of HTTP and JSON Schema. While this makes the specification more expressive, it also increases the burden on tooling and encourages API designs that are difficult to understand and consume.

To see how practical these features are, I tested a number of them using openapi-generator for Java, Go, Python, and TypeScript. The following sections introduce these features and discuss how well today's tooling supports them.

A Striking Historical Parallel

There is a striking historical parallel. The Web Services Description Language (WSDL) was the interface description language for Web Services. Together with XML Schema, it became a major source of complexity that ultimately contributed to the decline of the Web Services stack.

Most Web Services used only simple XML structures. Nevertheless, WSDL exposed the entire feature set of XML Schema for message modeling. Just because XML Schema supported inheritance, substitution groups, and other advanced constructs did not mean they were good choices for service design. Many of these features proved difficult or even impossible for tools to implement.

Why WSDL Failed

WSDL failed because the specification became more expressive than most developers and tools needed. Another contributing factor was the ambition to support every protocol and every edge case, even those that were rarely used.

REST Took the Opposite Approach

The REST APIs that followed took the opposite approach. They focused on a small, widely understood subset of HTTP and spread rapidly throughout the industry.

OpenAPI's Growing Scope

OpenAPI started with the same philosophy. Early versions described a practical subset of HTTP and JSON Schema that covered the vast majority of REST APIs. Over time, however, the specification has steadily expanded, incorporating more HTTP semantics and increasingly sophisticated JSON Schema features.

The remainder of this article examines several of these advanced features and evaluates how well they are supported by today's tooling.

OpenAPI Support for Advanced HTTP Features

Version 3.0 added support for advanced HTTP serialization mechanisms.

Passing Objects and Arrays as Query Parameters

Query parameters are usually simple key-value pairs. OpenAPI 3.0, however, allows arrays and even complex objects to be represented in the query string.

An array parameter can be encoded as repeated query parameters:

GET /cities?city=Paris&city=Berlin&city=Venice

Although this looks like three parameters, it actually represents a single array serialized as repeated keys. By changing the serialization style, the very same array could instead be represented as:

GET /cities?city[0]=Paris&city[1]=Berlin&city[2]=Venice

Objects can also be passed as query parameters:

GET /persons?name=Albert&birthdate=1879-03-14&profession=scientist

or, using a different serialization style:

GET /persons?person[name]=Albert&person[birthdate]=1879-03-14&person[profession]=scientist

OpenAPI lets API designers control these representations using the style and explode keywords.

Passing Objects application/x-www-form-urlencoded

HTML forms submit simple key-value pairs using the application/x-www-form-urlencoded media type. OpenAPI 3.0 extends this format by allowing complete object structures and arrays to be represented in form-encoded request bodies.

For example, the following request body represents a nested object with an array:

address[city]=Berlin&address[zip]=10115&tags[]=beta&tags[]=alpha

This feature primarily exists to describe existing protocols such as OAuth 2.0. It is rarely a good choice for new APIs.

Complex Parameter Serialization Styles

OpenAPI 3.0 supports a wide range of parameter serialization styles inherited from URI templates. Besides the familiar query string format, parameters can be encoded using styles such as matrix, label, pipeDelimited, spaceDelimited, and deepObject.

For example, the matrix style encodes parameters as path segments:

GET /users;id=3;id=4;id=5

To many developers, semicolons embedded in a URL path look more like a copy-and-paste error or even a SQL injection attempt than a legitimate API request. Nevertheless, this is a valid OpenAPI representation of an array parameter.

Multiple Content Types with Fundamentally Different Schemas for the Same Operation

OpenAPI allows each media type to define a completely different schema:

requestBody:
  content:
    application/json:
      schema:
        type: object
        properties:
          customer:
            type: string
    application/xml:
      schema:
        type: object
        properties:
          product:
            type: string

The JSON request contains a customer, while the XML request contains a product. Both belong to the same operation, but they represent fundamentally different data structures.

The previous examples expanded the HTTP model. OpenAPI 3.1 also adopted the full JSON Schema vocabulary, introducing another source of complexity.

Support for Advanced JSON Schema Features

Using not Schemas to Describe Valid Input

The not keyword defines a schema by specifying what a value must not be.

schema:
  not:
    type: string

This schema accepts any value except a string. Numbers, booleans, arrays, objects, and null may all be valid.

Although this is logically precise, it is difficult to represent in programming languages. What type should a code generator create for "anything except a string"? Most programming languages do not have a native type for such exclusions.

In our tests, generators for Java, JavaScript, Go, and Python failed to generate usable code for this schema. This illustrates the difference between a schema that can be validated and a schema that can be mapped cleanly to a programming-language type.

Using oneOf, anyOf, and allOf for Data Modeling and Inheritance

OpenAPI 3.0 introduced the JSON Schema composition keywords oneOf, anyOf, and allOf. They make it possible to model inheritance, polymorphism, and complex validation rules.

For example, allOf is often used to model inheritance:

components:
  schemas:
    Person:
      type: object
      properties:
        name:
          type: string

    Employee:
      allOf:
        - $ref: '#/components/schemas/Person'
        - type: object
          properties:
            employeeId:
              type: integer

Here, Employee inherits all properties of Person and adds an employeeId.

Nested Schema Composition

It's tempting to combine allOf, anyOf, and oneOf to express fine-grained validation rules.

allOf:
  - anyOf:
      - oneOf:
          - not:
              type: string
          - type: integer
      - type: boolean
  - type: object
    properties:
      id: { type: integer }

The generators tested reduced that schema to:

class Foo {
    int id;
}

Discriminators for Polymorphic Models

Like XML Schema before it, JSON Schema supports inheritance and polymorphic data models. The discriminator field petType identifies which concrete subtype is represented.

Pet:
  type: object
  properties:
    petType: { type: string }
  discriminator:
    propertyName: petType
    mapping:
      dog: '#/components/schemas/Dog'
      cat: '#/components/schemas/Cat'
Dog:
  allOf:
    - $ref: '#/components/schemas/Pet'
    - type: object
      properties:
        bark: { type: boolean }
Cat:
  allOf:
    - $ref: '#/components/schemas/Pet'
    - type: object
      properties:
        meow: { type: boolean }

This construct worked with the tested code generators. Swagger UI, however, had problems generating a representative sample request and clearly visualizing the resulting schema hierarchy.

JSON Schema Conditional Keywords Such as if, then, and else

JSON Schema 2020-12 lets you make validation rules conditional on the data itself. Use zipCode in the US and postalCode everywhere else.

if:
  properties: { country: { const: US } }
then:
  required: [zipCode]
else:
  required: [postalCode]

The code generators for all languages generated just an object with all three properties and ignored the test completely.

Using dependentRequired and dependentSchemas Constraints

A billingAddress is only required if a creditCard is present.

dependentRequired:
  creditCard: [billingAddress]

The code generators just dropped the dependency.

The examples above illustrate how expressive OpenAPI has become. The obvious question is how well today's tools support these capabilities.

Tool Support

Supporting an OpenAPI specification involves more than parsing YAML. Validators, documentation generators, client generators, server stubs, API gateways, testing tools, mocking frameworks, IDE plugins, and linters all need to understand the same features and produce compatible results.

Since the publication of OpenAPI 3.2 in September 2025, adoption has been slow. Ten months later, many popular tools still support only OpenAPI 3.1 or earlier. (Source: openapi.tools 2026-07-21)

Supported Versions in OpenAPI Tooling

To evaluate the current state of the ecosystem, I tested the examples from this article using openapi-generator 7.23.0 with the java (okhttp-gson), python (pydantic v2), typescript-axios, and go client generators. The result for the tests is showed in the tables.

HTTP: Request/Response Wire Serialization

Quirk Java Python TypeScript Go What breaks
Nested x-www-form-urlencoded Nested object flattened into a single field (Go: valid JSON; Python: dict repr; Java: debug toString()). Runtime problems with serialization.
matrix / label (path)
pipeDelimited / spaceDelimited ⚠️
deepObject (query) Only Go expands filter[city]=…; others send one stringified blob.
Multiple content types Only the first (JSON) schema is usable; the second is generated but orphaned and unreachable.

JSON Schema: Data Model & Validation

Quirk Java Python TypeScript Go What breaks
oneOf/anyOf ⚠️️ ✅️ ✅️ ✅️ Java: okhttp, jersey2, native work as expected. Heliodon, Resttemplate, microprofile and spring cause problems depending on the combined types.
discriminator on oneOf ⚠️ ⚠️ ⚠️ okhttp-gson ignores the discriminator and matches by field shape → petType:"dog" can deserialize as Cat. (The Spring server honors it via @JsonSubTypes.)
$dynamicRef / $dynamicAnchor Parser can't resolve $id-based $ref; $dynamicRef ignored → recursive element collapses to Object; the override child becomes an empty class.
Recursion via plain $ref
anyOf ⚠️ Java/Python enforce "≥1 match"; TypeScript merges into one interface so "at least one" is lost; Go = first-match.
not No model generated at all; type becomes Object/any. Constraint unenforced.
Nested allOf/anyOf/oneOf/not Collapses to { id }; every combinator branch dropped.
Discriminator (+ allOf children) ⚠️ ⚠️ Python: explicit value→class map. TypeScript: extends. Java: field defaulted to class name. Go: bare field.
Recursive self-$ref
if / then / else Conditional required rule silently dropped
dependentRequired / dependentSchemas Dependency rules silently dropped.
✅ faithful ⚠️ weakened / degraded ❌ dropped or wrong

The results show a clear pattern. Features introduced with OpenAPI 3.0 are generally well supported, while many of the more advanced JSON Schema capabilities introduced with OpenAPI 3.1 are only partially supported or ignored entirely. OpenAPI adopted the full JSON Schema 2020-12 vocabulary much faster than the tooling ecosystem could implement it.

What Worked Well

Not all advanced features caused problems. All tested generators handled recursive $ref definitions correctly, and allOf inheritance was consistently supported. Even features that were not implemented perfectly often degraded gracefully rather than producing unusable code.

Conclusion

The issue is not that these features exist. Every one of them has legitimate use cases. The problem is that an interface description language succeeds only when its ecosystem can faithfully implement it.

API designers can unintentionally create specifications that are perfectly valid according to the OpenAPI specification but cannot be faithfully implemented by today's tooling.

WSDL eventually became more expressive than the ecosystem surrounding it. Our experiments suggest that parts of OpenAPI are beginning to follow the same path.

A specification is only as useful as the ecosystem that implements it.


API Gateway eBook

Learn how to secure and manage APIs with a gateway in our free eBook.

API Gateway eBook Cover
Membrane API Gateway

The open-source Membrane API Gateway deploys APIs from OpenAPI and validates requests and responses against it.

Membrane API Gateway
API & OpenAPI Training

Deepen your API skills in our courses: