Endpoint content types

A simple path of the OpenAPI description will usually produce a single endpoint method in the target interface as described in the introduction.

OpenAPI allows us to define more complex apis that behave differently based on the request header. For example the following api definition can return its response in different formats. As json or as plain text:

openapi: 3.1.0
info:
  title: multiple response content types
  version: 1.0.0

paths:
  /foo:
    get:
      responses:
        '200':
          description: json or plain text result
          content:
            application/json:
                schema:
                  $ref: '#/components/schemas/Foo'
            text/plain:
                schema:
                  type: string
        default:
          description: error
          content:
            application/xml:
                schema:
                  $ref: '#/components/schemas/Error'

components:

  schemas:
    Foo:
      type: object
      properties:
        bar:
          type: string

    Error:
      type: object
      properties:
        error:
          type: string

A client request uses the request Accept header to tell the api which result content types it can handle.

Using a single endpoint method it has to decide which response to create.This leads to some boring if/else code. To avoid this the processor creates one endpoint method for each possible response.

multiple content types

For the example above (ignoring the default xml response) it creates the following interface:

package generated.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

public interface Api {

    @GetMapping( path = "/foo", produces = {"application/json"})
    Foo getFooApplicationJson();

    @GetMapping( path = "/foo", produces = {"text/plain"})
    String getFooTextPlain();

}

The apis normal response (status '200') can return the result as json or as plain text which leads to two methods for the same endpoint but with a different produces list in the mapping annotation.

One method when json gets requested and one when plain text gets requested. Spring will take care of selecting the correct endpoint.

multiple content types & default content type

In the (contrived) example our api does also define another content type for all other result status codes (usually the errors): xml.

If an endpoint returns multiple types, a success response (typically 200 ok) and at least one error response, the processor has to pick a return type for the endpoint methods.

With versions before 2021.5 the processor generates (by default) endpoint methods with an Object return value (or if generic something like ResponseType<?>) to handle the unrelated success and error response types.

This has the drawback that an important piece of information is missing: the success response type. With Object as return type it not clear what the success response type is.

With version 2021.5 it is possible to generate the endpoints with the success response type even with error responses. It is ignoring the error result types.

Since it is common practice to handle errors by throwing exceptions (e.g. in combination with the Spring @ResponseStatus annotation) the endpoint methods don’t need to handle different return types, and it is possible to simply use the type of the success response.

To switch between previous and new behavior there is a new mapping configuration to control the style of the return type named result-style. It has two possible values: success or all. This is available on all mapping levels new with 2025.2.

The default is success, i.e. the processor will automatically generate the code using the new behavior. In case the previous behavior is required, set the result-style to all.

openapi-processor-mapping: v12

options:
  package-name: ...

map:
  #result-style: success  # use the success result type, this is the default
  result-style: all # use an Object return type

new behavior, since 2021.5

Example of the new code, using result-style: success. This is the default if result-style is not set.

package generated.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

public interface Api {

    @GetMapping( path = "/foo", produces = {"application/json", "application/xml"})
    Foo getFooApplicationJson();

    @GetMapping( path = "/foo", produces = {"text/plain", "application/xml"})
    String getFooTextPlain();

}

previous behavior, before 2021.5

Example of the previous code, using result-style: all. The setting is required to generate the previous code.

package generated.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

public interface Api {

    @GetMapping( path = "/foo", produces = {"application/json", "application/xml"})
    Object getFooApplicationJson();

    @GetMapping( path = "/foo", produces = {"text/plain", "application/xml"})
    Object getFooTextPlain();

}

multiple responses with same content type

new with 2025.2

There is a case that was not properly handled before: if an endpoint of the OpenAPI description has multiple success responses with the same content type (e.g. application/json) but different response payloads.

The processor did honor only one response status using the last success response.

Here is an example OpenAPI:

openapi: 3.1.0
info:
  title: test multiple success responses
  version: 1.0.0

paths:
  /foo:
    get:
      description: endpoint with multiple success responses
      responses:
        '200':
          description: success
          content:
            application/json:
                schema:
                  $ref: '#/components/schemas/Foo'
        '202':
          description: another success
          content:
            application/json:
                schema:
                  $ref: '#/components/schemas/Bar'

components:
  schemas:

    Foo:
      type: object
      properties:
        foo:
          type: string

    Bar:
      type: object
      properties:
        bar:
          type: string

The two result payloads Foo and Bar are handled by two possible ways now.

Either by using Object as return type or by generating a marker interface that’s implemented by both payload and using it as the result type.

using Object as the result type

This is the default. To return either Foo or Bar from the endpoint method it uses Object as the return type.

package generated.api;

import annotation.Mapping;
import generated.support.Generated;

@Generated(value = "openapi-processor-core", version = "test")
public interface Api {

    @GetMapping("/foo")
    Object getFooApplicationJson();

}

using a marker interface as result type

An Object return type is obviously not very descriptive. It is impossible to know from the interface which results are possible.

To improve on that situation the processor can generate a marker interface that is more descriptive and helps with navigation in the IDE.

Generation of the marker interface is enabled by adding the response-interface option:

openapi-processor-mapping: v12

options:
  package-name: ...
  # ...
  response-interface: true

The generated interface will now look like this:

package generated.api;

import annotation.Mapping;
import generated.model.Foo;
import generated.model.GetFooBarAApplicationJsonResponse;
import generated.model.GetFooBarBApplicationJsonResponse;
import generated.support.Generated;

@Generated(value = "openapi-processor-core", version = "test")
public interface Api {

    @Mapping("/foo")
    GetFooApplicationJsonResponse getFooApplicationJson(); (1)

}
1 instead of returning Object it does return the marker interface type GetFooApplicationJsonResponse.

The marker interface is an empty interface and its name is derived from http method, path and content type to create a unique name.

package generated.model;

import generated.support.Generated;

@Generated(value = "openapi-processor-core", version = "test")
public interface GetFooApplicationJsonResponse {
}

The response payload classes Foo and Bar will both implement this interface:

package generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import generated.support.Generated;

@Generated(value = "openapi-processor-core", version = "test")
public class Foo implements GetFooApplicationJsonResponse {

    @JsonProperty("foo")
    private String foo;

    // ...
}

If the response type (Foo in this case) is used on multiple endpoints with multiple success response status it will implement multiple marker interfaces.

In case one of the success responses is a primitive result (like String) the processor will fall back to using Object because it can’t hide the primitive type behind a marker interface.

multiple content types, default content type & result wrapper

In case we (globally) enable a result wrapper, e.g. ResponseEntity in the mapping.yaml

map:
  result: org.springframework.http.ResponseEntity

the created code will now look like this:

package generated.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

public interface Api {

    @GetMapping(
            path = "/foo",
            produces = {"application/json", "application/xml"})
    ResponseEntity<?> getFooApplicationJson();

    @GetMapping(
            path = "/foo",
            produces = {"text/plain", "application/xml"})
    ResponseEntity<?> getFooTextPlain();

}

The response wraps the type by a ResponseEntity and to handle the multiple response types the generic parameter is the unknown type.