OpenAPI 3.2 with openapi-processor

19. October 2025

OpenAPI 3.2 was released last month. See Upgrading from OpenAPI 3.1 to 3.2 for an overview of the changes.

The newest openapi-processor-spring/micronaut 2025.6 release adds support for OpenAPI 3.2.

This article will show use of two enhancements of OpenAPI 3.2 with openapi-processor-spring:

the sample OpenAPI

Here is the sample OpenAPI

openapi.yaml
openapi: 3.2.0 (1)
$self: '../main/kotlin/io/openapiprocessor/samples/' (2)
info:
  title: openapi-processor-spring stream sample api
  version: 1.0.0

servers:
  - url: "https://openapiprocessor.io/{path}"
    variables:
      path:
        default: api

paths:
  /sse:
    $ref: 'sse/sse.yaml' (3)

  /jsonl:
    $ref: 'jsonl/jsonl.yaml' (3)

and the project directory layout.

directory structure, api and sources
sample
\---- src
      +---- api
      |     +---- mapping.yaml
      |     \---- openapi.yaml
      \---- main
            +---- kotlin
            |     \---- io
            |           \---- openapiprocessor
            |                 \---- samples
            |                       +---- jsonl
            |                       |     +---- JsonlController.kt
            |                       |     \---- jsonl.yaml (4)
            |                       +---- sse
            |                       |     +---- SseController.kt
            |                       |     \---- sse.yaml (4)
            |                       \ StreamApplication.kt
            \---- resources
                  \---- application.properties
1 the new OpenAPI version
2 this sets the base uri of the openapi.yaml document used for resolving relative references.
3 the $self allows to shorten the $ref uris of the endpoints and avoids repeating the full relative path from the openapi.yaml entry document.

Without $self the $ref s would look like this:

openapi.yaml
$ref: '../main/kotlin/io/openapiprocessor/samples/jsonl/jsonl.yaml'
$ref: '../main/kotlin/io/openapiprocessor/samples/jsonl/sse.yaml'
4 the OpenAPI path items of the endpoints. Putting them in a source package makes it possible to use the package name of the location as package name for generated files.

package-name-from-location describes this in more detail.

the streaming endpoints

We have two sample endpoints, one that provides a application/jsonl stream …​

jsonl.yaml
get:
  tags:
    - jsonl
  summary: stream objects
  description: endpoint has a stream response
  parameters:
    - name: source
      description: query, string
      in: query
      schema:
        type: string
  responses:
    '200':
      description: jsonl stream
      content:
        application/jsonl:
          itemSchema: (1)
            type: object
            properties:
              foo:
                type: string

and another one that provides server sent events:

sse.yaml
get:
  tags:
    - sse
  summary: stream objects
  description: endpoint has a stream response
  parameters:
    - name: source
      description: query, string
      in: query
      schema:
        type: string
  responses:
    '200':
      description: sse
      content:
        application/json:
          itemSchema: (1)
            type: object
            properties:
              foo:
                type: string
1 the itemSchema is a new keyword to describe the objects of the stream.

mapping configuration

With Spring Boot (MVC) we will use SseEmitter as endpoint return type to send server sent events and StreamingResponseBody to send JSON lines.

To tell the processor to use those classes as result type instead of the itemSchema we use a new variant of the result mapping. Everything else in the mapping.yaml is not important for this, just look at the map: at the end :-)

mapping.yaml
openapi-processor-mapping: v15

options:
  # shortcut to package-names.base
  # package-name: io.openapiprocessor.samples

  package-names:
    # same as package-name
    base: io.openapiprocessor.openapi
    # this enables package generation from the endpoint $ref file location
    location: io.openapiprocessor.samples

  # target-dir options
  target-dir:
    # add src/resource directories to target-dir
    layout: standard

  # create a property file with server/url path (requires standard target-dir layout)
  base-path:
    server-url: 0
    properties-name: api.properties

  # generate javadoc from OpenAPI description properties
  javadoc: true

  # enable code/javadoc formatting
  format-code: true

  # generate java records
  model-type: record

map:
  paths:
    /sse:
      result: plain => org.springframework.web.servlet.mvc.method.annotation.SseEmitter (1)

    /jsonl:
      result: plain => org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody (1)
1 the new result mapping: instead of providing a wrapper class with a single generic parameter, like ResponseEntity we use an arrow mapping:

plain ⇒ {target type}

which means, instead of the plain schema described in the OpenAPI response, use the target type as endpoint return type.

generated code

After running the processor we will get this for the JSONL endpoint:

JsonlApi.java
package io.openapiprocessor.samples.jsonl;

import io.openapiprocessor.openapi.support.Generated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

@Generated(value = "openapi-processor-spring", version = "2025.5")
public interface JsonlApi {

  /**
   * stream objects endpoint has a stream response
   *
   * @return jsonl stream
   */
  @GetMapping(path = "/jsonl", produces = {"application/jsonl"})
  StreamingResponseBody getJsonl(@RequestParam(name = "source", required = false) String source); (1)

}
JsonlGetResponse200.java
package io.openapiprocessor.samples.jsonl;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;

@Generated(value = "openapi-processor-spring", version = "2025.5")
public record JsonlGetResponse200(@JsonProperty("foo") String foo) {
}

Because the itemSchema is defined inline it gets a generated name, i.e. JsonlGetResponse200.

And this for the SSE endpoint:

SseApi.java
package io.openapiprocessor.samples.sse;

import io.openapiprocessor.openapi.support.Generated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@Generated(value = "openapi-processor-spring", version = "2025.5")
public interface SseApi {

  /**
   * stream objects endpoint has a stream response
   *
   * @return sse
   */
  @GetMapping(path = "/sse", produces = {"application/json"})
  SseEmitter getSse(@RequestParam(name = "source", required = false) String source); (1)

}
SseGetResponse200.java
package io.openapiprocessor.samples.sse;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;

@Generated(value = "openapi-processor-spring", version = "2025.5")
public record SseGetResponse200(@JsonProperty("foo") String foo) {
}

Again, because the itemSchema is defined inline it gets a generated name, i.e. SseGetResponse200.

the sample project

The sample project is available in the samples repository. It has a simple implementation of the two endpoints (which you should not use as reference implementation).

The sample has two HTTP client scripts (Intellij IDEA) to test each endpoint.

summary

This article used two new OpenAPI 3.2 features, $self & ìtemSchema that openapi-processor does support with the latest version. $self is used to simplify the path item $ref s and itemSchema is used to describe the streaming payloads. With a result mapping the processor generated the endpoint methods with te mapped streaming result types.