HTTP Client in Kotlin: Making Requests Properly from A to Z
\nModern development in Kotlin is unthinkable without interaction with remote servers. Whether it's a mobile application on Android or a server-side solution on Ktor — HTTP requests are the foundation of data exchange. In this article, we will cover how to create HTTP clients in Kotlin using best practices, popular libraries, and a modern approach with coroutines. You will learn how to make GET and POST requests, handle errors, work with JSON, and build a reliable network layer architecture.
\n\nWhy Kotlin is Ideal for HTTP Clients?
\nKotlin offers concise syntax, null safety, and full compatibility with the Java ecosystem. Thanks to coroutines, asynchronous requests become intuitive, without cumbersome callbacks. You can write sequential code that executes asynchronously — this significantly simplifies reading and debugging. Additionally, Kotlin is actively used in Android development, where network requests are a daily task.
\n\nMain Libraries for HTTP in Kotlin
\nCurrently, the three most popular approaches are:
\n- \n
- Ktor Client — a multiplatform client from JetBrains. Supports JVM, Android, iOS, JS, and Native. Ideal for projects requiring a single codebase. \n
- OkHttp + Retrofit — the classic stack for Android/JVM. OkHttp handles low-level network operations, while Retrofit generates typed interfaces. \n
- Fuel — a lightweight library with a functional approach. Suitable for small projects. \n
In this article, we will focus on Ktor and OkHttp, as they cover 90% of scenarios.
\n\nProject Setup and First Requests
\nFirst, let's add dependencies. If you are using Gradle (Kotlin DSL), add Ktor Client:
\ndependencies {\n implementation("io.ktor:ktor-client-core:2.3.7")\n implementation("io.ktor:ktor-client-cio:2.3.7") // CIO engine\n implementation("io.ktor:ktor-client-content-negotiation:2.3.7")\n implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")\n}\nFor OkHttp, the dependencies will be:
\ndependencies {\n implementation("com.squareup.okhttp3:okhttp:4.12.0")\n implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")\n implementation("com.google.code.gson:gson:2.10.1")\n}\n\nGET Request with Ktor Client
\nLet's create a simple client and make a GET request to a public API:
\nimport io.ktor.client.*\nimport io.ktor.client.request.*\nimport io.ktor.client.statement.*\n\nsuspend fun fetchData(): String {\n val client = HttpClient()\n val response: HttpResponse = client.get("https://jsonplaceholder.typicode.com/posts/1")\n val body = response.bodyAsText()\n client.close()\n return body\n}\n\n// Usage in a coroutine\nfun main() = runBlocking {\n println(fetchData())\n}\nNote: suspend is a Kotlin keyword for suspendable functions. This allows executing requests without blocking the thread.
GET Request with OkHttp
\nA similar example with OkHttp looks like this:
\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\n\nfun fetchDataSync(): String {\n val client = OkHttpClient()\n val request = Request.Builder()\n .url("https://jsonplaceholder.typicode.com/posts/1")\n .build()\n client.newCall(request).execute().use { response ->\n return response.body?.string() ?: "Error"\n }\n}\n\n// Call in a regular thread (blocking)\nfun main() {\n println(fetchDataSync())\n}\nOkHttp is synchronous by default, but it can be easily adapted for coroutines using suspendCancellableCoroutine.
POST Requests and Working with JSON
\nSending data is the second most important operation. Let's look at how to send JS