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 🙂