By Thomas Bayer
Published 22 July 2026
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.
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.
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.
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 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.
Version 3.0 added support for advanced HTTP serialization mechanisms.
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=VeniceAlthough 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]=VeniceObjects can also be passed as query parameters:
GET /persons?name=Albert&birthdate=1879-03-14&profession=scientistor, using a different serialization style:
GET /persons?person[name]=Albert&person[birthdate]=1879-03-14&person[profession]=scientistOpenAPI lets API designers control these representations using the style and explode keywords.
application/x-www-form-urlencodedHTML 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[]=alphaThis feature primarily exists to describe existing protocols such as OAuth 2.0. It is rarely a good choice for new APIs.
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=5To 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.
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: stringThe 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.
not Schemas to Describe Valid InputThe not keyword defines a schema by specifying what a value must not be.
schema:
not:
type: stringThis 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.
oneOf, anyOf, and allOf for Data Modeling and InheritanceOpenAPI 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: integerHere, Employee inherits all properties of Person and adds an employeeId.
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;
}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.
if, then, and elseJSON 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.
dependentRequired and dependentSchemas ConstraintsA 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.
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)
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.
| 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. |
| 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. |
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.
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.
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.
By Thomas Bayer
Published 22 July 2026
The open-source Membrane API Gateway deploys APIs from OpenAPI and validates requests and responses against it.
Membrane API GatewayDeepen your API skills in our courses: