Skip to content

8.4.0: generate every OpenAPI 3.2 operation (QUERY, additionalOperations, HEAD/OPTIONS/TRACE) and stop dropping 3.2 contract parts silently - #474

Merged
jemacineiras merged 2 commits into
sngular:mainfrom
joseegman-idoneea:feature/openapi-3.2-no-silent-loss
Sep 30, 2026
Merged

jemacineiras merged 2 commits into
sngular:mainfrom
joseegman-idoneea:feature/openapi-3.2-no-silent-loss

Conversation

@joseegman-idoneea

@joseegman-idoneea joseegman-idoneea commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The engine accepted openapi: 3.2.0, but running a 3.2 contract for each new feature through it showed parts dropped without a word, or crashing the generator:

3.2 contract Before Now
query operation dropped generated
additionalOperations (e.g. PURGE) dropped generated
head, options, trace (not new in 3.2) dropped generated
in: querystring servers bound it by accident; clients took it and never sent it form-exploded query object, everywhere
$ref to components/mediaTypes NullPointerException resolved
response summary without description empty description used as the description
application/json-seq, text/event-stream a single JSON document with produces = "application/json" the generation fails, until they are generated

Methods Spring's RequestMethod has no constant for

@RequestMapping only takes RequestMethod's eight methods, so servers map QUERY and the additionalOperations methods with a generated annotation:

@HttpMethodMapping(method = "QUERY", value = "/items", produces = {"application/json"}, consumes = {"application/json"})
default ResponseEntity<List<ItemDTO>> queryItems(@Valid @RequestBody FilterDTO filterDTO) {

The generated HttpMethodMappingConfiguration (Spring MVC or WebFlux variant) registers a RequestMappingHandlerMapping subclass. It builds a RequestMappingInfo with a custom method condition from that annotation, and maps nothing else. It does not replace Spring's mapping (no WebMvcRegistrations), so it cannot clash with an application's own configuration.

The @HttpMethodMapping operations carry no @RequestMapping, so Spring's mapping ignores them. The generated mapping comes first, and a request of another method falls through to Spring's (POST /items is still Spring's 405). For a request of its own method it cannot serve, it answers as Spring would (406/415).

These operations also declare consumes, because Spring checks a @RequestBody's content type only for POST/PUT/PATCH. Without it, a QUERY with a text/plain body got a 400 "body missing" instead of a 415.

It is found by component scanning of the API package, or through @Import(HttpMethodMappingConfiguration.class).

Clients send these methods with HttpMethod.valueOf(...) or @HttpExchange(method = "QUERY"). Spring Framework 5's HttpMethod is an enum, so generating a Spring Boot 2 client fails for them.

Behaviour changes

  • Contracts with head/options/trace operations now get those methods generated. They were silently missing before.
  • json-seq/SSE contracts now fail the generation. They used to generate code that could not serve the stream. SSE is the next PR.

Known limits (README)

  • The generated RestClient class needs an ApiRestClient on a RestClient, or on a RestTemplate with a factory that sends custom methods. The default HttpURLConnection rejects QUERY ("Invalid HTTP method: QUERY", checked).
  • springdoc does not list the @HttpMethodMapping operations.
  • Spring MVC and Tomcat refuse TRACE by default.

Test plan

  • New fixture testOpenApi32. It covers:

    • query with a body;
    • options, head, and PURGE via additionalOperations;
    • in: querystring with a $ref schema;
    • a components/mediaTypes $ref;
    • summary-only responses.

    Generated for the Spring MVC server (Boot 2, Boot 4), WebFlux (Boot 2 getMethodValue(), Boot 3 getMethod().name()), RestClient, WebClient and @HttpExchange.

  • Error tests: a Boot 2 client with QUERY; application/json-seq; text/event-stream; a querystring that is not form-encoded

  • No existing asset changes

  • Engine: 267 tests green, checkstyle green; Maven plugin ITs green; Gradle test integrationTest green (JDK 21)

  • Runtime check outside the repo, over real HTTP:

    • servers, 6/6 each on Spring MVC 2.7.18 (configuration imported), WebFlux 2.7.18, WebFlux 3.5.8 and Spring MVC 4.0.8 (configuration found by scanning). The six checks:
      • QUERY with a JSON body filters;
      • PURGE 204;
      • GET still served, POST/DELETE still Spring's 405;
      • QUERY with text/plain gets 415;
      • HEAD and OPTIONS with headers;
      • querystring bound per property.
    • clients on 4.0.8, 4/4: the generated RestClient class on a RestClient, the WebClient class and a blocking @HttpExchange send QUERY, PURGE, HEAD, OPTIONS and the query string. The default RestTemplate fails as documented.
    • the NDJSON runtime suite still passes on 2.7, 3.5 and 4.0.
  • README: new "OpenAPI 3.2 operations" section

  • Version bumped to 8.4.0 in all 5 places

🤖 Generated with Claude Code

@codacy-production

codacy-production Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 43 complexity

Metric Results
Complexity 43

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@jemacineiras jemacineiras self-assigned this Sep 29, 2026
@jemacineiras jemacineiras added enhancement New feature or request release Adding this tag to a PR will cause a release on merge labels Sep 29, 2026
joseegarcia and others added 2 commits September 29, 2026 16:38
…parts silently

Operations: head, options and trace were dropped, and so were OpenAPI 3.2's
query and additionalOperations. Every Path Item operation is now generated.
Servers map the methods Spring's RequestMethod has no constant for (QUERY,
PURGE...) with a generated @HttpMethodMapping, which a generated
HttpMethodMappingConfiguration maps through a RequestMappingHandlerMapping of
its own (Spring MVC and WebFlux, Boot 2 to 4): it maps only those methods, so
Spring still answers the others, 405 included, and it answers 406/415 for a
request of its method it cannot serve. Such operations declare what they
consume, since Spring reads a @RequestBody's content type only for
POST/PUT/PATCH. Clients send them with HttpMethod.valueOf; Spring Boot 2
clients, whose HttpMethod is an enum, fail the generation instead.

Also:
- an in: querystring parameter, a form-encoded schema of the whole query
  string, is read as that object sent form-exploded (any other media type
  fails); clients used to take it and never send it;
- a $ref to components/mediaTypes is resolved (it crashed the generator);
- a response without description is documented with its summary;
- application/json-seq and text/event-stream fail the generation, as their
  items were read as a single JSON document.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@joseegman-idoneea
joseegman-idoneea force-pushed the feature/openapi-3.2-no-silent-loss branch from f670dad to ab59c61 Compare September 29, 2026 14:40
@jemacineiras
jemacineiras merged commit b858e8b into sngular:main Sep 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request release Adding this tag to a PR will cause a release on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants