openapi-processor & Spring Boot 3 with Kotlin

3. March 2024

Although openapi-processor generates java interface and pojo/record source files it works very well with Kotlin on the JVM.

In many cases it is not necessary to have a generator that outputs the same language used to implement the project.

getting started

Setup is the same as usual, configure the processor to run in your gradle or maven build. See the documentation or the Kotlin sample (the sample contains a bit more code than described here).

Next we create our OpenAPI document, run the processor (openapi-processor-spring) and implement the generated java interfaces in Kotlin.

This is all straight forward. No magic involved.

OpenAPI

Here is the sample api. To make it a little bit more interesting it contains an endpoint that uses paging.

openapi.yaml
openapi: 3.1.0
info:
  title: openapi-processor-spring sample api
  version: 1.0.0

paths:
  /bar:
    post:
      tags:
        - bar
      summary: echo a Bar.
      description: simple sample endpoint.
      operationId: echoBar
      requestBody:
        $ref: '#/components/requestBodies/BarBody'
      responses:
        '200':
          description: the echoed Bar
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Bar'

  /bars:
    get:
      tags:
        - bar
      summary: get Bar pages.
      description: simple sample endpoint.
      parameters:
        - in: query
          name: pageable
          required: false
          schema:
            $ref: '#/components/schemas/Pageable'
      responses:
        '200':
          description: the pages of Bars
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BarPage'

components:

  schemas:

    Foo:
      type: object
      properties:
        foo:
          type: string
          maxLength: 10
        id:
          type: string
          format: uuid
        bar:
          $ref: '#/components/schemas/Bar'

    Bar:
      type: object
      properties:
        bar:
          type: string
          maxLength: 20

    Pageable: (1)
      description: minimal Pageable query parameters
      type: object
      properties:
        page:
          type: integer
        size:
          type: integer

    Page: (2)
      description: minimal Page response without content property
      type: object
      properties:
        number:
          type: integer
        size:
          type: integer

    BarContent: (3)
      description: specific content List of the Page response
      type: object
      properties:
        content:
          type: array
          items:
            $ref: '#/components/schemas/Bar'

    BarPage: (4)
      description: typed Page
      type: object
      allOf:
        - $ref: '#/components/schemas/Page'
        - $ref: '#/components/schemas/BarContent'


  requestBodies:

    BarBody:
      description: the Bar object that is echoed
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Bar'

The api contains a few objects that represent the Spring paging objects:

1 Pageable, the incoming page properties, i.e. org.springframework.data.domain.Pageable
2 Page, the common page properties of a page result, i.e. org.springframework.data.domain.Page except the content property
3 BarContent represents the generic property of the Page
4 BarPage combines Page and BarContent with allOf to create the final page response dto.

We do all this to describe the API for someone that reads the API.

It is not necessary for the code generation itself.

Since we are using Spring we want to use Spring’s page classes instead of the dtos created by the processor from the OpenAPI description. To do that we tell the processor to use them instead of the types used in the OpenAPI description.

For whatever reason we also don’t want to use the Bar schema described in the OpenAPI. We already have a nice and short Kotlin version of Bar so we want to use it instead of the generated java record for Bar (this is just for demonstration, we could simply use the generated Bar dto).

That’s done by adding a few type mappings to the processor configuration.

Here is the full mapping file:

mapping.yaml
openapi-processor-mapping: v6

options:
  # the root package of the generated interfaces/model. The package folder tree will be created
  # inside {targetDir} configured in the project file. The generated interfaces and models  will
  # be placed into the "api" and "model" subpackages of packageName:
  #  - interfaces => "${packageName}.api"
  #  - models => "${packageName}.model"
  package-name: io.openapiprocessor.openapi

  # generate javadoc from OpenAPI description properties
  javadoc: true

  # run the generated code through a code formatter
  format-code: true

  # use bean validations with jakarta package name
  bean-validation: jakarta

  # use java records instead of pojos
  model-type: record

map:
  types:
    - type: Bar => io.openapiprocessor.samples.Bar (1)
    - type: Pageable => org.springframework.data.domain.Pageable (2)
    - type: BarPage => org.springframework.data.domain.Page<io.openapiprocessor.samples.MappedBar> (2)
1 this one tells the processor to use our kotlin Bar instead of generating a java Bar record.
2 and this two map the paging schemas from the OpenAPI to Spring`s java types.

Next we look at the generated java interface.

the generated java interface

The generated java interface of our API is shown below.

It uses Spring`s page types and also happily uses our Kotlin version of Bar. No problem here with referencing the Kotlin class from the generated java interface.

BarApi.java
package io.openapiprocessor.openapi.api;

import io.openapiprocessor.openapi.support.Generated;
import io.openapiprocessor.samples.Bar;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

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

    /**
     * echo a Bar.
     * simple sample endpoint.
     *
     * @return the echoed Bar
     */
    @PostMapping(path = "/bar", consumes = {"application/json"}, produces = {"application/json"})
    Bar echoBar(@RequestBody @Valid @NotNull Bar body); (1)

    /**
     * get Bar pages.
     * simple sample endpoint.
     *
     * @return a pages of Bar
     */
    @GetMapping(path = "/bars", produces = {"application/json"})
    Page<Bar> getBars(@Valid Pageable pageable); (2)

}
1 here it is using the Kotlin Bar class.
2 here it is using Spring’s pageable types.
Bar.kt
package io.openapiprocessor.samples

import jakarta.validation.constraints.NotNull

data class Bar(@NotNull val bar: String = "bar")

What’s left is the Kotlin implementation of the generated java interface.

Kotlin implementation

We can simply implement the interface as if it is a Kotlin interface.

Bar.kt
package io.openapiprocessor.samples

import io.openapiprocessor.openapi.api.BarApi
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.web.bind.annotation.RestController
import java.util.*

@RestController
class ApiController: BarApi {

    override fun echoBar(bar: Bar): Bar {
        return bar
    }

    override fun getBars(pageable: Pageable?): Page<Bar> {
        return PageImpl(listOf(
            Bar("value 1"),
            Bar("value 2")
        ))
    }
}

What is important here is that it hides all the Spring and Bean Validation annotation noise and that I don’t have to fight with the annotations to get the details right. :-)

From my experience I can tell that you don’t look very often at the generated interfaces. Why care if it is Java or Kotlin? If I can work with Kotlin I should be qualified to read and understand the Java interfaces. :-)

summary

This little article described how the openapi-processor can easily be used in a Kotlin project. It also mapped an OpenAPI type to a Kotlin class that is referenced in the generated java API interface. No problem. Kotlin does handle the mixed Kotlin Java code.

To learn more about openapi-processor and how to generate controller interfaces and model classes from an OpenAPI description take a look at the documentation.