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: 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:
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.
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. |
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.
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.