<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ktor Client Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/ktor-client/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 17 Jan 2024 05:15:00 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.codersee.com/wp-content/uploads/2025/04/cropped-codersee_logo_circle_2-32x32.png</url>
	<title>Ktor Client Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How To Create a Ktor Client To Connect To OpenWeatherMap API</title>
		<link>https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/</link>
					<comments>https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/#respond</comments>
		
		<dc:creator><![CDATA[Peter Lantukh]]></dc:creator>
		<pubDate>Wed, 17 Jan 2024 05:15:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Ktor Client]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9508613</guid>

					<description><![CDATA[<p>This article provides all the necessary information you need to know about setting up a Ktor client with OpenWeatherMap API.</p>
<p>The post <a href="https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/">How To Create a Ktor Client To Connect To OpenWeatherMap API</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>This comprehensive guide is about setting up a Ktor client that effortlessly retrieves data from OpenWeatherMap API. I&#8217;ll tackle client configuration, API calls, models configuration, and <strong>error handling</strong>, leaving you with a weather-fetching client ready to be used on any platform.</p>



<p><strong>Ktor</strong> is a flexible library that provides the ability to create a non-blocking <strong>HTTP client</strong>, which enables you to perform requests and handle responses. Its functionality could be enhanced with plugins, such as logging, serialization, authentication, and so on. Its core benefit is that it&#8217;s a lightweight framework that can be used on different platforms such as <strong>Android, JVM, or Native</strong>!</p>



<h2 class="wp-block-heading" id="h-general-overview">General Overview</h2>



<p>Let&#8217;s discuss some basic information for everyone to be on the same page.  </p>



<p>Today, we are going to write HTTP requests and receive responses. There are different types of requests but OpenWeatherMap for the most part utilizes <strong>GET requests</strong> with various parameters.</p>



<p>OpenWeatherMap API, as you can tell by its name, is an API for retrieving weather-related data. It provides various types of data but we&#8217;re going to use only 3 of them:</p>



<ul class="wp-block-list">
<li>Current Weather, </li>



<li>3-hours Forecast, </li>



<li>and Air Pollution API.</li>
</ul>



<p>To start using OpenWeatherMap <a href="https://home.openweathermap.org/subscriptions/unauth_subscribe/onecall_30/base" target="_blank" rel="noreferrer noopener nofollow">you need to register</a> and get your special API key. It has lots of free options and for our sake, we don&#8217;t need to pay at all. </p>



<p>But be aware of the limitations of the call: <strong>1,000 API calls per day for free</strong>!</p>



<h2 class="wp-block-heading" id="h-gradle-dependencies">Gradle Dependencies</h2>



<p>For this project, we&#8217;re going to use basic <strong>Ktor&#8217;s client, serialization, engines, and logging</strong>. </p>



<p>Your <strong>build.gradle.kts</strong> file would look like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val ktor_version: String by project
val kotlin_version: String by project
val logback_version: String by project

plugins {
    kotlin("jvm") version "1.9.21"
    id("io.ktor.plugin") version "2.3.6"
    id("org.jetbrains.kotlin.plugin.serialization") version "1.9.21"
    application
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.ktor:ktor-client-core-jvm")
    implementation("io.ktor:ktor-client-cio-jvm")

    implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
    implementation("io.ktor:ktor-client-content-negotiation-jvm")

    implementation("ch.qos.logback:logback-classic:$logback_version")
    implementation("io.ktor:ktor-client-logging:$ktor_version")
    implementation("io.ktor:ktor-client-apache5:$ktor_version")

    testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}</pre>



<h2 class="wp-block-heading" id="h-creating-models">Creating Models</h2>



<p>The first step of creating a Ktor client is actually revealing what we&#8217;re gonna handle. Basically, we need <strong>2</strong> types of models: <strong>request and response</strong>. All necessary information is located on the corresponding page of the documentation. The documentation is pretty neat and, generally, it&#8217;s a good practice to check it out during the coding!</p>



<p>To optimize the process we could spot similar parameters appearing in the Current Weather and Weather Forecast. Such as Mode and Units. </p>



<p>So we can create separate classes and use the latter like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">enum class ResponseUnits {
    STANDARD,
    METRIC,
    IMPERIAL
}

enum class ResponseMode {
    XML
}
</pre>



<p>Now let&#8217;s create the request themselves. </p>



<p>As there are some optional parameters present we&#8217;re going to make them nullable with default value null so that we could use them or not with ease. </p>



<p>Here are the models for requests:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class CurrentWeatherRequest(
    val latitude: Double,
    val longitude: Double,
    val mode: ResponseMode? = null,
    val units: ResponseUnits? = null,
    val language: String? = null
)

data class WeatherForecastRequest(
    val latitude: Double,
    val longitude: Double,
    val mode: ResponseMode? = null,
    val units: ResponseUnits? = null,
    val language: String? = null,
    val count: Int? = null
)

data class AirPollutionRequest(
    val latitude: Double,
    val longitude: Double
)</pre>



<p>Consequently, we desperately need models to put our data to: <strong>the response models</strong>. </p>



<p>It&#8217;s the most routine process because there are some huge responses and by &#8216;huge&#8217; I mean lots of parameters. Let&#8217;s dive deep into one model and the rest would be easy to create. </p>



<p>Here&#8217;s what our <strong>JSON</strong> of current weather data looks like (there is also a text representation of this JSON with more information about parameters):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "coord": {
    "lon": 10.99,
    "lat": 44.34
  },
  "weather": [
    {
      "id": 501,
      "main": "Rain",
      "description": "moderate rain",
      "icon": "10d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 298.48,
    "feels_like": 298.74,
    "temp_min": 297.56,
    "temp_max": 300.05,
    "pressure": 1015,
    "humidity": 64,
    "sea_level": 1015,
    "grnd_level": 933
  },
  "visibility": 10000,
  "wind": {
    "speed": 0.62,
    "deg": 349,
    "gust": 1.18
  },
  "rain": {
    "1h": 3.16
  },
  "clouds": {
    "all": 100
  },
  "dt": 1661870592,
  "sys": {
    "type": 2,
    "id": 2075663,
    "country": "IT",
    "sunrise": 1661834187,
    "sunset": 1661882248
  },
  "timezone": 7200,
  "id": 3163858,
  "name": "Zocca",
  "cod": 200
}</pre>



<p>Now we have to face some difficulties. Not only do we have to parse it but also there are some <strong>optional parameters</strong>. </p>



<p>To solve the first issue we mark our classes with <strong>@Serializable</strong> and <strong>@SerialName</strong> annotations. I highly recommend using @SerialName because it provides <strong>enhanced readability</strong> and usability especially when handling such long classes. </p>



<p>To solve the second problem I recommend you to read my <a href="https://blog.codersee.com/kotlinx-serialization-in-kotlin-all-you-need-to-know/" target="_blank" rel="noreferrer noopener">detailed guide</a> that covers all essential information about Serialization. Basically, if we want to parse all of the data, we make <strong>optional fields</strong> with <strong>@EncodeDefault</strong> and make it <strong>nullable with the default value as null</strong>. This makes sure that if there was no such a field we would get a null, without any exceptions. </p>



<p>Now let&#8217;s look at classes for our Ktor client:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class CurrentWeather(
    @SerialName("coord") val coordinates: Coordinates,
    @SerialName("weather") val weather: List&lt;Weather&gt;,
    @SerialName("base") val base: String,
    @SerialName("main") val main: Main,
    @SerialName("visibility") val visibility: Int,
    @SerialName("wind") val wind: Wind,
    @SerialName("rain")
    @EncodeDefault
    val rain: Rain? = null,
    @SerialName("snow")
    @EncodeDefault
    val snow: Snow? = null,
    @SerialName("clouds") val clouds: Clouds,
    @SerialName("dt") val dateTime: Long,
    @SerialName("sys") val systemData: SystemData,
    @SerialName("timezone") val timezone: Int,
    @SerialName("id") val cityId: Int,
    @SerialName("name") val cityName: String,
    @SerialName("cod") val code: Int
) {

    //other classes here

    @Serializable
    data class Wind(
        @SerialName("speed") val speed: Double,
        @SerialName("deg") val direction: Int,
        @SerialName("gust") val gust: Double
    )

    @Serializable
    data class Rain(
        @SerialName("1h")
        @EncodeDefault
        val oneHour: Double? = null,
        @SerialName("3h")
        @EncodeDefault
        val threeHours: Double? = null
    )

	//other classes here
}</pre>



<p>The rest of the responses are quite similar so you could check them out in the repository that comes with this article. </p>



<p>Quick remark, I&#8217;ve made classes such as Coordinates, Weather, etc. as inner classes since there are the same classes for other responses but with different structures. </p>



<p>This approach helps not to mix them up.</p>



<h2 class="wp-block-heading" id="h-creating-the-ktor-client-itself">Creating The Ktor Client Itself</h2>



<p>We are gonna be using <strong>Ktor&#8217;s HttpClient</strong> with some adjustments and extended configuration. </p>



<p>Let&#8217;s check it out:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val client = HttpClient(Apache5) {
        install(ContentNegotiation) {
            json(Json {
                isLenient = false
            })
        }
        install(Logging)
        defaultRequest {
            url {
                protocol = URLProtocol.HTTPS
                host = "api.openweathermap.org/data/2.5"
            }
        }
    }</pre>



<p>First of all, we have the <strong>Apache5</strong> engine rather than <strong>CIO</strong>, for example, because it&#8217;s suitable for <strong>HTTP/2</strong> while CIO is not. </p>



<p>Next, we have 2 plugins: the <strong>ContentNegotiation</strong> for the <strong>JSON</strong> parsing and the <strong>Logging</strong> for detailed logs. </p>



<p>Also, as all of our requests to OpenWeatherMap have similar endpoints, we can use <strong>defaultRequest</strong> to shorten our request&#8217;s links. The whole defaultRequest translates to https://api.openweathermap.org/data/2.5/. We&#8217;ll combine this with the request&#8217;s specific <strong>parameters</strong>. </p>



<p>Now let&#8217;s talk about them.</p>



<h2 class="wp-block-heading" id="h-creating-routes">Creating Routes</h2>



<p>As all of our routes would share similar logic let&#8217;s look closely at only one of them for the Current Weather:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun getCurrentWeather(currentWeatherRequest: CurrentWeatherRequest): CurrentWeather? {
        return try {
            val response: HttpResponse = client
                .get("/weather") {
                    url {
                        parameters.append("lat", currentWeatherRequest.latitude.toString())
                        parameters.append("lon", currentWeatherRequest.longitude.toString())
                        parameters.append("appid", APP_ID)
                        if (currentWeatherRequest.mode != null) parameters.append(
                            "mode",
                            currentWeatherRequest.mode.name()
                        )
                        if (currentWeatherRequest.units != null) parameters.append(
                            "units",
                            currentWeatherRequest.units.name()
                        )
                        if (currentWeatherRequest.language != null) parameters.append(
                            "lang",
                            currentWeatherRequest.language
                        )
                    }
                }

            if (response.status == HttpStatusCode.OK) {
                response.body&lt;CurrentWeather&gt;()
            } else {
                println("Failed to retrieve current weather. Status: ${response.status}")
                null
            }
        } catch (e: Exception) {
            println("Error retrieving current weather: ${e.message}")
            null
        }
    }</pre>



<p>Here we create <strong>GET HttpResponse</strong> with the path &#8220;/weather&#8221; and then we specify <strong>additional parameters</strong>, note that it&#8217;ll automatically use the correct notation(? and other). </p>



<p>In the URL block, we list all of our parameters using <strong>parameters.append()</strong> function that takes the name and the value. There is no need to include <strong>optional parameters</strong> that are not specified, so we simply check whether they&#8217;re null or not.</p>



<p>Our Ktor client also implements <strong>error handling</strong>. </p>



<p>For the errors with the request itself, such as connection problems, any issues from the server, etc. we&#8217;ll get the &#8220;Error retrieving current weather:&#8221; message and null result. </p>



<p>And if your request is successful but has some undesired response, in our case everything except 200 OK, we&#8217;ll get &#8220;Failed to retrieve current weather. Status:&#8221; and null result as well.</p>



<h2 class="wp-block-heading" id="h-ktor-client-in-action">Ktor Client In Action</h2>



<p>To use the client you could simply call our object with the corresponding method like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">WeatherClient.getCurrentWeather(
        CurrentWeatherRequest(
            latitude = 44.34,
            longitude = 10.99,
            units = ResponseUnits.IMPERIAL,
            language = "pl"
        )
    )
	
	WeatherClient.getWeatherForecast(
        WeatherForecastRequest(
            latitude = 44.34,
            longitude = 10.99,
            units = ResponseUnits.IMPERIAL,
            language = "pl"
        )
    )
	
	WeatherClient.getAirPollution(
        AirPollutionRequest(
            latitude = 50.0,
            longitude = 50.0
        )
    )</pre>



<p>This could be used on every platform but as I&#8217;ve mentioned before make sure you&#8217;re using the correct engine!</p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that’s all for this article about the <strong>OpenWeatherMap API Ktor client</strong>.</p>



<p>If you would like to download the full source code, then you can find it in this <a href="https://github.com/codersee-blog/kotlin-ktor-client-weather">GitHub repository</a>.</p>



<p>Lastly, thank you for being here, and happy to hear your feedback in the comments section below!</p>
<p>The post <a href="https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/">How To Create a Ktor Client To Connect To OpenWeatherMap API</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How To Use kotlinx.serialization with Ktor and Kotlin?</title>
		<link>https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/</link>
					<comments>https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/#respond</comments>
		
		<dc:creator><![CDATA[Peter Lantukh]]></dc:creator>
		<pubDate>Tue, 02 Jan 2024 06:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[kotlinx.serialization]]></category>
		<category><![CDATA[Ktor Client]]></category>
		<category><![CDATA[Ktor Server]]></category>
		<category><![CDATA[Serialization]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008447</guid>

					<description><![CDATA[<p>This article is all you need to know about how to set up kotlinx.serialization library in Ktor Server, Client, and WebSockets. </p>
<p>The post <a href="https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/">How To Use kotlinx.serialization with Ktor and Kotlin?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you would like to learn how to use the <strong>kotlinx.serialization library with Ktor</strong> and Kotlin, then you just came to the right place! In this article, I will show you how to configure it to work with: </p>



<ul class="wp-block-list">
<li>Ktor Server</li>



<li>Ktor Client </li>



<li>WebSockets in Ktor</li>
</ul>



<p>Before we start, I just wanted to note that we are going to use a modified project from the lesson about <a href="https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/" target="_blank" rel="noreferrer noopener">Ktor with Ktorm and PostgreSQL</a>. So if you don&#8217;t know how to set up and use a Ktor server with PostgreSQL you should definitely check it up, because I will skip the basics. Additionally, if you&#8217;d like to explore kotlinx.serialization in details, then check out my <a href="https://blog.codersee.com/kotlinx-serialization-in-kotlin-all-you-need-to-know/" target="_blank" rel="noreferrer noopener">detailed guide</a>.</p>



<p>Lastly, please note that there is a <strong>GitHub repository</strong> for all of this in the end of this lesson!</p>



<h2 class="wp-block-heading" id="h-setup">Setup</h2>



<p>A few quick words about setting up. Note that for the main thing we are going to use, such as <strong>ContentNegotiation</strong>, Ktor has <strong>different implementations for Client and Server</strong>. </p>



<p>Do not mess them up, it causes a lot of trouble and a lot of Stack Overflow questions 🙂</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">//server
implementation("io.ktor:ktor-server-content-negotiation-jvm")
//client
implementation("io.ktor:ktor-client-content-negotiation-jvm")</pre>



<h2 class="wp-block-heading" id="h-kotlinx-serialization-with-ktor-server">kotlinx.serialization With Ktor Server</h2>



<p>Now, let&#8217;s start with the kotlinx.serialization for the Ktor server part. </p>



<p>I am going to use <strong>Postman</strong> for making HTTP<strong> requests</strong> and testing the server. You can use any tool for this purpose even the client we will write about in the next part 🙂</p>



<p>First of all, let&#8217;s check our classes for the User from previous parts:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">interface User : Entity&lt;User> {  
  companion object : Entity.Factory&lt;User>()  
  
  val userId: Long?  
  var userName: String  
}</pre>



<p>&nbsp;Now we are going to need classes for our responses:&nbsp;</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Serializable  
data class UserResponse(  
  val userId: Long,  
  val userName: String  
)  
  
@Serializable  
data class UserRequest(  
  val userName: String  
)

@Serializable  
data class UserErrorResponse(val message: String)</pre>



<p>Basically, the central part of all serialization here is <strong>ContentNegotiation</strong> which could be installed for our server(not the client). </p>



<p>As we are using it for Json serialization, we should specify it as <strong>json()</strong> like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureSerialization() {  
  install(ContentNegotiation) {  
    json(Json {  
      prettyPrint = true  
      isLenient = true  
    })  
  }  
}</pre>



<p>And here&#8217;s our loved <strong>Json</strong> object! Now we can do all kinds of trickery that we know and learned before.</p>



<p>For the code above, I&#8217;ve used a simple style specification, but you can use it as you like. As an example, we can add a custom or polymorphic serializer:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val contextualSerializerModule = SerializersModule {
  contextual&lt;Date>(DateAsStringSerializer)
}

private val polymorphicSerializationModule = SerializersModule {
  polymorphic(User::class) {
    subclass(Admin::class, Admin.serializer())
    subclass(Guest::class, Guest.serializer())
  }
}

object DateAsStringSerializer : KSerializer&lt;Date> {
  private val dateFormat = SimpleDateFormat("yyyy-MM-dd 'T' HH:mm:ss.SSSZ")

  override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(
    "Date", 
    PrimitiveKind.STRING,
  )

  override fun serialize(encoder: Encoder, value: Date) {
    encoder.encodeString(dateFormat.format(value))
  }

  override fun deserialize(decoder: Decoder): Date {
    return dateFormat.parse(decoder.decodeString())
  }
}
</pre>



<p>And our server is going to look like this:&nbsp;</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureSerialization() {
  install(ContentNegotiation) {
    json(Json {
      prettyPrint = true
      isLenient = true
      serializersModule = contextualSerializerModule
      serializersModule = polymorphicSerializationModule
    })
  }
}</pre>



<p>We can remove this for now, we&#8217;ll use actual polymorphic serialization in the Web Sockets section.</p>



<p>Now, what about routes? Let&#8217;s check out a simple <strong>POST</strong> request:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Route.createUser(userService: UserService) {
  post {

    val request = call.receive&lt;UserRequest>()

    val success = userService.createUser(userRequest = request)

    if (success)
      call.respond(HttpStatusCode.Created)
    else
      call.respond(HttpStatusCode.BadRequest, UserErrorResponse("Cannot create user"))
  }
}</pre>



<p>As we can see, we don&#8217;t serialize/deserialize anything manually. </p>



<p>The whole process happens due to this line:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val request = call.receive&lt;UserRequest>()</pre>



<p>We can witness that our request deserializes into the UserRequest as such is marked with <strong>@Serializable</strong> annotation. </p>



<p>The same happens here:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Route.updateUserByIdRoute(userService: UserService) {
  patch("/{userId}") {
    val userId: Long = call.parameters["userId"]?.toLongOrNull()
      ?: return@patch call.respond(
          HttpStatusCode.BadRequest, 
          UserErrorResponse("Invalid id")
        )

    val request = call.receive&lt;UserRequest>()
    val success = userService.updateUserById(userId, request)

    if (success)
      call.respond(HttpStatusCode.NoContent)
    else
      call.respond(
        HttpStatusCode.BadRequest,
        UserErrorResponse("Cannot update user with id [$userId]"),
      )
  }
}</pre>



<p>Our request is deserialized and we can update a user. </p>



<p>Now let&#8217;s test it and get our users:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private fun User?.toUserResponse(): UserResponse? =  
    this?.let { UserResponse(it.userId!!, it.userName) }

fun Route.getAllUsersRoute(userService: UserService) {  
    get {  
        val users = userService.findAllUsers()  
            .map(User::toUserResponse)  
  
        call.respond(message = users)  
    }  
}</pre>



<p>As I said before, I’m going to use Postman. </p>



<p>So how about checking our server:</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="432" height="108" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_4.png" alt="Image shows a GET request URL in Postman. " class="wp-image-9008448" style="aspect-ratio:4;width:432px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_4.png 432w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_4-300x75.png 300w" sizes="(max-width: 432px) 100vw, 432px" /></figure>



<figure class="wp-block-image aligncenter size-full is-resized"><img fetchpriority="high" decoding="async" width="474" height="280" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_6.png" alt="Image shows a response from the GET request serialized with kotlinx serialization in our Ktor server" class="wp-image-9008449" style="aspect-ratio:1.6928571428571428;width:474px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_6.png 474w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_6-300x177.png 300w" sizes="(max-width: 474px) 100vw, 474px" /></figure>



<p>Post:</p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="391" height="112" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_5-1.png" alt="Image shows the URL for POST request in Postman" class="wp-image-9008451" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_5-1.png 391w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_5-1-300x86.png 300w" sizes="(max-width: 391px) 100vw, 391px" /></figure>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="990" height="150" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2.png" alt="Screenshot presents the content type header set to application/json" class="wp-image-9008452" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2.png 990w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2-300x45.png 300w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2-768x116.png 768w" sizes="auto, (max-width: 990px) 100vw, 990px" /></figure>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="388" height="124" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_3.png" alt="Screenshot shows the response we get from the POST endpoint in our Ktor server serialized using the kotlinx serialization library" class="wp-image-9008453" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_3.png 388w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_3-300x96.png 300w" sizes="auto, (max-width: 388px) 100vw, 388px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="181" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-1024x181.png" alt="Image presents 201 Created response from the POST endpoint" class="wp-image-9008454" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-1024x181.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-300x53.png 300w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-768x136.png 768w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1.png 1501w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Delete:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="349" height="102" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_7.png" alt="Image shows the url path for the DELETE endpoint." class="wp-image-9008455" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_7.png 349w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_7-300x88.png 300w" sizes="auto, (max-width: 349px) 100vw, 349px" /></figure>



<h2 class="wp-block-heading" id="h-kotlinx-serialization-with-ktor-client">kotlinx.serialization With Ktor Client</h2>



<p>Now the kotlinx.serialization for the Ktor client. Here&#8217;s the same thing, but make sure you don&#8217;t miss anything and use <strong>ContentNegotiation FOR CLIENT</strong>. </p>



<p>Check the import:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.client.plugins.contentnegotiation.*</pre>



<p>We can install it just like before:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val client = HttpClient(CIO) {
  install(ContentNegotiation) {
    json(Json {
      prettyPrint = true
      isLenient = true
    })
  }
  defaultRequest {
    url {
      host = "0.0.0.0"
      path("/")
      port = 8080
    }
  }}
</pre>



<p>All things apply here as well, so let&#8217;s check how to use it for our requests. There are lots of possible situations, we are going to cover basic data retrieving and how to place something inside a <strong>body</strong> of a request.</p>



<p>We want to get all of the users, what should we do? We can use something like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun getAllUsers(): List&lt;UserResponse> {
  return try {
    val response: HttpResponse = client.get("/users")

    if (response.status == HttpStatusCode.OK) {
      response.body&lt;List&lt;UserResponse>>()
    } else {
      println("Failed to retrieve users. Status: ${response.status}")
      emptyList()
    }
  } catch (e: Exception) {
    println("Error retrieving users: ${e.message}")
    emptyList()
  }
}</pre>



<p>The main part here as well is this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">response.body&lt;List&lt;UserResponse>>()</pre>



<p>The UserResponse class can be serialized, and we don&#8217;t need to do anything at all. All hail the <strong>ContentNegotiation</strong> 🙂</p>



<p>How about we create some user? We can do it as well:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun createUser(user: UserRequest) {
  try {
    val response: HttpResponse = client.post("/users") {
      contentType(ContentType.Application.Json)
      setBody(user)
    }

    if (response.status == HttpStatusCode.Created) {
      println("User created successfully")
    } else {
      println("Failed to create user. Status: ${response.status}")
    }
  } catch (e: Exception) {
    println("Error creating user: ${e.message}")
  }
}</pre>



<p>This time, we specify the content type for our body, such as <strong>ContentType.Application.Json</strong> and just set our user in here. All happens as it is a miracle.</p>



<p>Now let’s test it. I’m going to use this simply to check each method:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">runBlocking {

  val allUsersOld = UserRequests.getAllUsers()
  println("All Users: $allUsersOld")

  val newUser = UserRequest(
    userName = "Mark"
  )

  UserRequests.createUser(newUser)

  val allUsersNew = UserRequests.getAllUsers()
  println("All Users: $allUsersNew")

  val userIdToRetrieve = 2L
  val retrievedUser = UserRequests.getUserById(userIdToRetrieve)
  println("User with ID $userIdToRetrieve: $retrievedUser")

  val userIdToUpdate = 2L
  val updatedUser = UserRequest(
    userName = "Bob"
  )
  UserRequests.updateUserById(userIdToUpdate, updatedUser)

  val userIdToDelete = 2L
  UserRequests.deleteUserById(userIdToDelete)
}</pre>



<p>And the corresponding result would be something like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">...
All Users: [UserResponse(userId=2, userName=User #2)]
...
User created successfully
...
All Users: [UserResponse(userId=2, userName=User #2), UserResponse(userId=7, userName=Mark)]
...
User with ID 2: UserResponse(userId=2, userName=User #2)
...
User updated successfully
...
User deleted successfully</pre>



<p>I’ve skipped the logs because they are not important for this.&nbsp;</p>



<h2 class="wp-block-heading" id="h-web-sockets">Web Sockets</h2>



<p>So, here&#8217;s the fun part. <strong>There is no Content Negotiation for the Web Sockets</strong> 🙁 </p>



<p>Nonetheless, there is such thing as <strong>contentConverter</strong> for WebSockets:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val client = HttpClient(CIO).config {
  install(WebSockets) {
    contentConverter = KotlinxWebsocketSerializationConverter(
      Json
    )
  }
}</pre>



<p>Basically, that&#8217;s it, now we can use <strong>sendSerialized()</strong> and <strong>receiveDeserialized()</strong> to send and receive data.</p>



<p>However, let&#8217;s focus more on manual implementation for the sake of understanding. Here we have classes to represent our messages:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Serializable
abstract class Message {
  abstract val content: String
}

@Serializable
@SerialName("text")
class TextMessage(override val content: String) : Message()

@Serializable
@SerialName("system")
class SystemMessage(override val content: String, val systemInfo: String) : Message()

private val module = SerializersModule {
  polymorphic(Message::class) {
    subclass(TextMessage::class, TextMessage.serializer())
    subclass(SystemMessage::class, SystemMessage.serializer())
  }
}

val messagesFormat = Json {
  serializersModule = module
}</pre>



<p>We are going to use TextMessage to send to the server and SystemMessage to send back from the server. </p>



<p>To send a message we must serialize it to string. And to receive vice versa. Nothing difficult: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@OptIn(ExperimentalSerializationApi::class)
private suspend fun DefaultClientWebSocketSession.receiveMessage() {
  try {
    for (message in incoming) {
      message as? Frame.Text ?: continue
      val deserializedMessage: Message =
        messagesFormat.decodeFromStream(message.data.inputStream())
      println("${deserializedMessage.content} // ${(deserializedMessage as? SystemMessage)?.systemInfo}")
    }
  } catch (e: Exception) {
    println("Error while receiving: " + e.localizedMessage)
  }
}

private suspend fun DefaultClientWebSocketSession.sendMessage(message: Message) {
  val serializedMessage = messagesFormat.encodeToString(message)
  try {
    send(serializedMessage)
  } catch (e: Exception) {
    println("Some error occur: " + e.localizedMessage)
    return
  }
}</pre>



<p>Here&#8217;s our simple server that going to get our message and send it back with some changes:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">routing {
  webSocket("/hello") {
    try {
      for (frame in incoming) {
        frame as? Frame.Text ?: continue
        val deserializedMessage: WebSocketSession.Message =
          WebSocketSession.messagesFormat.decodeFromStream(frame.data.inputStream())
        val newMessageText = deserializedMessage.content + " - from Client"
        val serializedMessage = WebSocketSession.messagesFormat.encodeToString&lt;WebSocketSession.Message>(
          WebSocketSession.SystemMessage(
            content = newMessageText,
            systemInfo = "Important"
          )
        )
        Connection(this).session.send(serializedMessage)
      }
    } catch (e: Exception) {
      println(e.localizedMessage)
    }
  }
}</pre>



<p>The final part is to test it. We simply connect it to our already existing server:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun main() {
  embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
    configureUserRoutes()
    configureSerialization()
    configureSockets()
  }.start(wait = true)
}</pre>



<p>And now run the client, for example, I’ve created this one:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun connectWebSocket() {
  client.webSocket(
    host = "0.0.0.0",
    port = 8080,
    path = "/hello"
  ) {
    launch { sendMessage(TextMessage("Good morning!")) }
    launch { receiveMessage() }
    delay(2000)
    launch { sendMessage(TextMessage("Hello!")) }
    launch { receiveMessage() }
    delay(2000)
    println("Connection closed. Goodbye!")
    client.close()
  }
}

runBlocking {
  WebSocketSession.connectWebSocket()
}</pre>



<p>Here’s the results:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">2023-12-01 15:46:26.104 [DefaultDispatcher-worker-4] TRACE io.ktor.websocket.WebSocket - Sending Frame TEXT (fin=true, buffer len = 41) from session io.ktor.websocket.DefaultWebSocketSessionImpl@2e8ab815
2023-12-01 15:46:26.110 [DefaultDispatcher-worker-6] TRACE io.ktor.websocket.WebSocket - WebSocketSession(StandaloneCoroutine{Active}@4ecfdc65) receiving frame Frame TEXT (fin=true, buffer len = 82)
Good morning! - from Client // Important
2023-12-01 15:46:28.094 [DefaultDispatcher-worker-6] TRACE io.ktor.websocket.WebSocket - Sending Frame TEXT (fin=true, buffer len = 34) from session io.ktor.websocket.DefaultWebSocketSessionImpl@2e8ab815
2023-12-01 15:46:28.097 [DefaultDispatcher-worker-3] TRACE io.ktor.websocket.WebSocket - WebSocketSession(StandaloneCoroutine{Active}@4ecfdc65) receiving frame Frame TEXT (fin=true, buffer len = 75)
Hello! - from Client // Important
Connection closed. Goodbye!</pre>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this article about <strong>kotlinx.serialization with Ktor</strong>. </p>



<p>If you would like to download the full source code, then you can find it in <a href="https://github.com/codersee-blog/kotlin-ktor-kotlinx-serialization" target="_blank" rel="noreferrer noopener">this GitHub repository</a>. </p>



<p>Lastly, thank you for being here, and happy to hear your feedback <strong>in the comments section below</strong>!</p>
<p>The post <a href="https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/">How To Use kotlinx.serialization with Ktor and Kotlin?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-05-15 22:18:49 by W3 Total Cache
-->