Generate Kotlin Client From OpenAPI Specs

This photo is a featured image for "Generate Kotlin Client from OpenAPI specs" article and consist of Kotlin logo in the foreground and blurred desktop setup in the background.

Hey hey hey ๐Ÿ™‚ ! In this article, I will show you how easily we can generate a Kotlin client from any OpenAPI Spec (both 2.0 and 3.0) and save us hours of work.

During this tutorial, we will learn:

  • what are OpenAPI specs and what problem do they solve,
  • why and when code generation might be a good idea for your project,
  • how to generate Kotlin clients using different libraries, like Ktor, Retrofit, or Spring WebClient.

Video Tutorial

If you prefer video content, then check out my video:

If you find this content useful, please leave a subscription  ๐Ÿ˜‰

What are OpenAPI Specs

OpenAPI, formerly known as Swagger, is a specification for describing and documenting RESTful APIs.

Imagine that each person or team would describe created APIs in their own, custom manner. Confluence pages, READMEs, text files, graphic charts, and many many more. Probably millions of different ways to describe the same thing.

Thankfully, OpenAPI solves this problem and allows us to describe and document HTTP APIs in a standardized manner so that both the creators and consumers can be on the same page.

What does it look like? Well, let’s take a look at the example we will be working with today:

openapi: 3.0.0
info:
  version: 1.0.0
  title: JSON Placeholder API
  description: See https://jsonplaceholder.typicode.com/
paths:
  /posts:
    get:
      description: Returns all posts
      tags: ["Posts"]
      operationId: "getPosts"
      responses:
        "200":
          description: Successful response
          content:
            "application/json":
              schema:
                $ref: "#/components/schemas/PostsList"

  /posts/{id}:
    get:
      description: Returns a post by id
      tags: ["Posts"]
      operationId: "getPost"
      parameters:
        - name: id
          in: path
          required: true
          description: The user id.
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Successful response
          content:
            "application/json":
              schema:
                $ref: "#/components/schemas/Post"
        "404":
          description: Post not found

components:
  schemas:
    PostsList:
      "type": "array"
      "items":
        $ref: "#/components/schemas/Post"
    Post:
      "type": "object"
      "required":
        - "id"
        - "userId"
        - "title"
      "properties":
        id:
          type: "integer"
        userId:
          type: "integer"
        title:
          type: "string"
        completed:
          type: "boolean"

As we can see, the OpenAPI spec is a YAML file (but can be a JSON too) which is a language-agnostic instruction of the API.

Moreover, given its standardized structure and format, it can be easily read not only by humans, but machines too, which we will see in this article in action ๐Ÿ™‚

When And Why Code Generation Might Be A Good Idea?

As software engineers, we automate things, so that they can be done faster, easier, and with less amount of errors.

You probably see where I’m going now ๐Ÿ™‚

Whenever we do things manually, we need more time, we make more mistakes, and we distract ourselves from things that we are actually good at.

And among plenty of areas in our codebase, HTTP clients might be a good place to start with. This is the area, where the only thing that we care about is fetching/sending the data in an appropriate format, and where we end up with lots of boilerplate code.

Solution- OpenAPI Generator

And in order to generate a client in Kotlin we can use the OpenAPI generator.

And although in this article we will focus on the clients, this generator can be used to create servers and documentation for over 50 languages and frameworks, too. If you would like to check the full list, then you can find it right here.

But how exactly it is going to help us?

Well, in simple words, with this tool we can convert the OpenAPI specification file into the ready-to-use Kotlin classes with WebClient/OkHTTP/Retrofit implementation of HTTP clients and data classes for response and request objects.

Generate Kotlin Client- Gradle

One of the easiest ways to add the OpenAPI generator to our codebase will be the Gradle plugin.

Let’s navigate to the build.gradle.kts file and add the following:

plugins {
    // other plugins
    id("org.openapi.generator") version "7.0.1"
}

Nextly, let’s sync our project.

When it’s done, we will see the following tasks added to our project:

  • openApiGenerate– to generate code via Open API Tools Generator for Open API 2.0 or 3.x specification documents,
  • openApiGenerators– to lists generators available via Open API Generators,
  • openApiMeta– to generate a new generator to be consumed via Open API Generator,
  • openApiValidate– which we can use to validate an Open API 2.0 or 3.x specification document.

Add OpenAPI Spec To The Project

As the next step, let’s add the OpenAPI spec to the project.

In our example, we will use the json-placeholder-api.yaml file we saw already and put it inside the $root/openapi directory.

Following, let’s navigate to build.gradle.kts and alter the openApiGenerate task:

openApiGenerate {
    inputSpec.set("$rootDir/openapi/json-placeholder-api.yaml")
    generatorName.set("kotlin")
}

The above configs are the only two required settings: we need to specify the input spec and the generator we would like to use.

Note: at the moment, we can specify only one input spec (and quite possible it will be already fixed by the time you will read this blog post). But if that’s not the case, then you can use the merger plugin.

And if you are wondering what library will the “Kotlin” generator use by default, then at the moment of writing, it is OkHttp 4.2.0.

Change the HTTP Client Library

But can we change the HTTP client library? Yes, of course.

We can use one of the following instead:

  • jvm-ktor
  • jvm-okhttp4 (default)
  • jvm-okhttp3
  • jvm-spring-webclient
  • jvm-retrofit2
  • multiplatform
  • jvm-volley
  • jvm-vertx

And the only thing we need to do with our gradle plugin is to set the library property:

openApiGenerate {
    inputSpec.set("$rootDir/openapi/json-placeholder-api.yaml")
    generatorName.set("kotlin")

    library.set("jvm-retrofit2")
}

Of course, we can customize plenty of other settings in the OpenAPI generator plugin. To see the whole list of available properties, please refer to the Kotlin generator docs.

Moreover, it’s worth mentioning that the generator plugin does not import necessary dependencies to the project. It is our responsibility and depending on the library we need to add them to the project.

For example, when dealing with the default client we must import okhttp3 and moshi:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.11.0")
    implementation("com.squareup.moshi:moshi:1.15.0")
    implementation("com.squareup.moshi:moshi-kotlin:1.15.0")

    #other dependencies
}

Using Generated Code

With all of that being done, let’s run the openApiGenerate task, for example, using the gradle wrapper:

 ./gradlew openApiGenerate

By default, our tool puts all the files inside the $root/build/generate-resources/src directory.

Moreover, the default package name is main.kotlin.org.openapitools.client, which of course can be customized according to our needs.

When we check the content, we should see plenty of files generated for us:

Of course, this will vary depending on the library we use, but for Retrofit, we will see among others this:

import okhttp3.Call
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Converter
import retrofit2.CallAdapter
import retrofit2.converter.scalars.ScalarsConverterFactory
import com.squareup.moshi.Moshi
import retrofit2.converter.moshi.MoshiConverterFactory


class ApiClient(
    private var baseUrl: String = defaultBasePath,
    private val okHttpClientBuilder: OkHttpClient.Builder? = null,
    private val serializerBuilder: Moshi.Builder = Serializer.moshiBuilder,
    private val callFactory : Call.Factory? = null,
    private val callAdapterFactories: List<CallAdapter.Factory> = listOf(
    ),
    private val converterFactories: List<Converter.Factory> = listOf(
        ScalarsConverterFactory.create(),
        MoshiConverterFactory.create(serializerBuilder.build()),
    )
) {
 // code
}

And the interface handling two endpoints:

import org.openapitools.client.infrastructure.CollectionFormats.*
import retrofit2.http.*
import retrofit2.Call
import okhttp3.RequestBody
import com.squareup.moshi.Json

import org.openapitools.client.models.Post

interface PostsApi {
    /**
     * 
     * Returns a post by id
     * Responses:
     *  - 200: Successful response
     *  - 404: Post not found
     *
     * @param id The user id.
     * @return [Call]<[Post]>
     */
    @GET("posts/{id}")
    fun getPost(@Path("id") id: kotlin.Long): Call<Post>

    /**
     * 
     * Returns all posts
     * Responses:
     *  - 200: Successful response
     *
     * @return [Call]<[kotlin.collections.List<Post>]>
     */
    @GET("posts")
    fun getPosts(): Call<kotlin.collections.List<Post>>

}

Can we start using it already? Not really.

If we try to use these classes now in our code, they won’t be found.

In gradle, we must add the generated directory to the sourceSets:

sourceSets {
    main {
        java {
            srcDir("${buildDir}/generate-resources/main/src")
        }
    }
}

And with that done, we can finally make use of the code:

import org.openapitools.client.apis.PostsApi

fun main() {
  val api = PostsApi(basePath = "https://jsonplaceholder.typicode.com")

  api.getPosts()
    .forEach(::println)
}

Make Generation a Part of Compilation

Before I wrap this article, I would like to show you one more thing.

Whenever we try to run the code without the generated files, it will fail. There’s no point of doing that manually each time either.

So, as a solution, we can alter the compileKotlin flow:

tasks.compileKotlin{
    dependsOn("openApiGenerate")
}

In simple words, the openApiGenerate task will be always invoked before the compileKotlin.

Summary

And that’s all for this article on how to generate a Kotlin HTTP client using the OpenAPI generator.

I hope that this introduction will convince you to give it a try and will be a good help in your first steps.

As always, you can find the source code in this GitHub repository.

Have a great week and don’t forget to leave a comment ๐Ÿ™‚

Share this:

Hi there! ๐Ÿ‘‹

Hi there! ๐Ÿ‘‹

My name is Piotr and I've created Codersee to share my knowledge about Kotlin, Spring Framework, and other related topics through practical, step-by-step guides. Always eager to chat and exchange knowledge.

Related content

Newsletter

Image presents 3 ebooks with Java, Spring and Kotlin interview questions.

Never miss any important updates from the Kotlin world and get 3 ebooks!

You may opt out any time. Terms of Use and Privacy Policy

To make Codersee work, we log user data. By using our site, you agree to our Privacy Policy and Terms of Use.