OpenAPITools / OpenAPITools/openapi-generator
[BUG][openapi-yaml] Merged spec reorders HTTP operations under the same path, breaking intended method ordering
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
When using the openapi-yaml generator to produce a merged (flattened) OpenAPI specification with all $ref references resolved, the HTTP operations (methods) under each path are reordered alphabetically instead of preserving the original declaration order from the source YAML file.
For example, if a path defines operations in the order GET, POST, PUT, PATCH, DELETE, the merged output reorders them to DELETE, GET, PATCH, POST, PUT. This silently breaks the intended ordering despite the source OpenAPI files being defined correctly.
We maintain internal API documentation conventions that prescribe a specific HTTP method order (e.g., CRUD: GET -> POST -> PUT -> PATCH -> DELETE). The merged spec is used as the source of truth for downstream API consumers and documentation tools (e.g., Swagger). Because the generator reorders operations, consumers see them in an unexpected sequence, leading to confusion and inconsistency between the authored spec and the rendered documentation.
openapi-generator version
Reproduced with v7.14.0. The issue likely affects all versions of the openapi-yaml generator that do not set skipSortingOperations.
OpenAPI declaration file content or url
Input spec (sample-api-v1.yaml):
openapi: 3.0.3
info:
title: Sample API
version: 1.0.0
tags:
- name: orders
description: Order management operations
paths:
/orders:
get:
tags:
- orders
summary: List all orders
operationId: listOrders
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Order'
post:
tags:
- orders
summary: Create a new order
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
'201':
description: order created
/orders/{orderId}:
get:
tags:
- orders
summary: Get order by ID
operationId: getOrderById
parameters:
- name: orderId
in: path
required: true
schema:
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
put:
tags:
- orders
summary: Update an order
operationId: updateOrder
parameters:
- name: orderId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
'200':
description: order updated
patch:
tags:
- orders
summary: Partially update an order
operationId: patchOrder
parameters:
- name: orderId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
'200':
description: order patched
delete:
tags:
- orders
summary: Delete an order
operationId: deleteOrder
parameters:
- name: orderId
in: path
required: true
schema:
type: string
responses:
'204':
description: order deleted
components:
schemas:
Order:
type: object
required:
- id
properties:
id:
type: string
description:
type: string
status:
type: string
enum:
- pending
- completed
- cancelled
Generation Details
java -jar openapi-generator-cli.jar generate \
-i sample-api-v1.yaml \
-g openapi-yaml \
-o ./output
Or via Gradle plugin:
tasks.register<GenerateTask>("generateMergedSpec") {
generatorName.set("openapi-yaml")
inputSpec.set("$projectDir/api/sample-api-v1.yaml")
outputDir.set("${layout.buildDirectory.get().asFile}/merged-spec")
configOptions.put("outputFile", "sample-api-v1.yaml")
}
Steps to reproduce
- Save the YAML spec above as
sample-api-v1.yaml. - Generate the merged spec using the
openapi-yamlgenerator (via CLI or Gradle plugin). - Open the generated output file (e.g.
build/merged-spec/sample-api-v1.yamlfor the Gradle example, oroutput/openapi/openapi.yamlfor the CLI). - Inspect the HTTP methods under
/orders/{orderId}.
Expected: Operations preserve the source file order:
/orders/{orderId}:
get:
...
put:
...
patch:
...
delete:
...
Actual: Operations are reordered alphabetically by HTTP method:
/orders/{orderId}:
delete:
...
get:
...
patch:
...
put:
...
This causes documentation tools to display operations in an unintuitive order (DELETE before GET), creating confusion for API consumers who see a different ordering than what was intentionally defined in the source spec.
Related issues/PRs
- #22166 This issue was fixed for
python-fastapiby enablingskipSortingOperations, but the same problem exists in theopenapi-yamlgenerator. - #12950 Open feature request for similar behavior in the HTML generator.
- #19550 Related fix for another generator addressing path operation ordering.
Suggest a fix
The openapi-yaml generator (OpenAPIYamlGenerator) should override getSkipSortingOperations() to return true by default, similar to the fix applied in #22163 for python-fastapi.
The openapi-yaml generator is primarily used to resolve $ref references and produce a flat, self-contained YAML file. Sorting operations is counterproductive in this use case, as the purpose is to faithfully represent the original spec with resolved references, not to rearrange it.
Alternatively, a global skipSortingOperations config option could be exposed so users of any generator can opt out of operation sorting.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in the OpenAPIYamlGenerator implementation and inspect how getSkipSortingOperations is handled for the openapi-yaml generator. Reproduce the issue with the supplied sample-api-v1.yaml and CLI or Gradle commands, then verify that the generated methods under /orders/{orderId} retain the source order while references are still resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, openapi
- Domain
- api, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100