diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fbd4096590..97c090caf4 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -53,6 +53,7 @@ autoService-compiler = { module = "com.google.auto.service:auto-service", versio
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.8.1" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+kotlinx-serialization-jsonOkio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinx-serialization" }
kotlinx-serialization-proto = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
okhttp-client = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-loggingInterceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
diff --git a/retrofit-converters/kotlinx-serialization-json/README.md b/retrofit-converters/kotlinx-serialization-json/README.md
new file mode 100644
index 0000000000..cabd31de76
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/README.md
@@ -0,0 +1,38 @@
+# kotlinx.serialization Converter
+
+A `Converter` which uses [kotlinx.serialization.json][1] for serialization.
+
+Given a `Json`, call `asConverterFactory()` in order to
+create a `Converter.Factory`.
+
+```kotlin
+val retrofit = Retrofit.Builder()
+ .baseUrl("https://example.com/")
+ .addConverterFactory(Json.asConverterFactory())
+ .build()
+```
+
+
+## Download
+
+Download [the latest JAR][2] or grab via [Maven][3]:
+```xml
+
+ com.squareup.retrofit2
+ converter-kotlinx-serialization-json
+ latest.version
+
+```
+or [Gradle][3]:
+```groovy
+implementation 'com.squareup.retrofit2:converter-kotlinx-serialization-json:latest.version'
+```
+
+Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
+
+
+
+ [1]: https://github.com/Kotlin/kotlinx.serialization
+ [2]: https://search.maven.org/remote_content?g=com.squareup.retrofit2&a=converter-kotlinx-serialization-json&v=LATEST
+ [3]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.squareup.retrofit2%22%20a%3A%22converter-kotlinx-serialization-json%22
+ [snap]: https://s01.oss.sonatype.org/content/repositories/snapshots/
diff --git a/retrofit-converters/kotlinx-serialization-json/build.gradle b/retrofit-converters/kotlinx-serialization-json/build.gradle
new file mode 100644
index 0000000000..959199d0d5
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'org.jetbrains.kotlin.jvm'
+apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
+apply plugin: 'com.vanniktech.maven.publish'
+apply plugin: 'org.jetbrains.dokka'
+
+dependencies {
+ api projects.retrofit
+ api libs.kotlinx.serialization.jsonOkio
+
+ testImplementation libs.junit
+ testImplementation libs.okhttp.mockwebserver
+}
diff --git a/retrofit-converters/kotlinx-serialization-json/gradle.properties b/retrofit-converters/kotlinx-serialization-json/gradle.properties
new file mode 100644
index 0000000000..df23cbe97e
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/gradle.properties
@@ -0,0 +1,3 @@
+POM_ARTIFACT_ID=converter-kotlinx-serialization-json
+POM_NAME=Converter: kotlinx.serialization.json
+POM_DESCRIPTION=A Retrofit Converter which uses kotlinx.serialization.json for serialization.
diff --git a/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/Factory.kt b/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/Factory.kt
new file mode 100644
index 0000000000..d75e00d7a0
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/Factory.kt
@@ -0,0 +1,52 @@
+package retrofit2.converter.kotlinx.serialization.json
+
+import java.lang.reflect.Type
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.serializer
+import okhttp3.RequestBody
+import okhttp3.ResponseBody
+import retrofit2.Converter
+import retrofit2.Retrofit
+
+@ExperimentalSerializationApi
+internal class Factory(
+ private val json: Json,
+) : Converter.Factory() {
+
+ @Suppress("RedundantNullableReturnType") // Retaining interface contract.
+ override fun responseBodyConverter(
+ type: Type,
+ annotations: Array,
+ retrofit: Retrofit,
+ ): Converter? {
+ val loader = serializer(type)
+ return ResponseBodyConverter(json, loader)
+ }
+
+ @Suppress("RedundantNullableReturnType") // Retaining interface contract.
+ override fun requestBodyConverter(
+ type: Type,
+ parameterAnnotations: Array,
+ methodAnnotations: Array,
+ retrofit: Retrofit,
+ ): Converter<*, RequestBody>? {
+ val saver = serializer(type)
+ return RequestBodyConverter(json, saver)
+ }
+
+ private fun serializer(type: Type) = json.serializersModule.serializer(type)
+}
+
+/**
+ * Return a [Converter.Factory] which uses Kotlin serialization for Json-based payloads.
+ *
+ * Because Kotlin serialization is so flexible in the types it supports, this converter assumes
+ * that it can handle all types. If you are mixing this with something else, you must add this
+ * instance last to allow the other converters a chance to see their types.
+ */
+@ExperimentalSerializationApi
+@JvmName("create")
+fun Json.asConverterFactory(): Converter.Factory {
+ return Factory(this)
+}
diff --git a/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/RequestBodyConverter.kt b/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/RequestBodyConverter.kt
new file mode 100644
index 0000000000..f42bc17339
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/RequestBodyConverter.kt
@@ -0,0 +1,20 @@
+package retrofit2.converter.kotlinx.serialization.json
+
+import kotlinx.serialization.SerializationStrategy
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType
+import okhttp3.RequestBody
+import retrofit2.Converter
+
+internal class RequestBodyConverter(
+ private val json: Json,
+ private val saver: SerializationStrategy,
+) : Converter {
+
+ private val contentType = MediaType.get("application/json; charset=UTF-8")
+
+ override fun convert(value: T): RequestBody {
+ val string = json.encodeToString(saver, value)
+ return RequestBody.create(contentType, string)
+ }
+}
diff --git a/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/ResponseBodyConverter.kt b/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/ResponseBodyConverter.kt
new file mode 100644
index 0000000000..d5f8cd0972
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/src/main/java/retrofit2/converter/kotlinx/serialization/json/ResponseBodyConverter.kt
@@ -0,0 +1,20 @@
+package retrofit2.converter.kotlinx.serialization.json
+
+import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.okio.decodeFromBufferedSource
+import okhttp3.ResponseBody
+import retrofit2.Converter
+
+@ExperimentalSerializationApi
+class ResponseBodyConverter(
+ private val json: Json,
+ private val loader: DeserializationStrategy,
+) : Converter {
+
+ override fun convert(value: ResponseBody): T {
+ val source = value.source()
+ return json.decodeFromBufferedSource(loader, source)
+ }
+}
diff --git a/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryContextualListTest.kt b/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryContextualListTest.kt
new file mode 100644
index 0000000000..4a2641d33f
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryContextualListTest.kt
@@ -0,0 +1,87 @@
+package retrofit2.converter.kotlinx.serialization.json
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.contextual
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import retrofit2.Call
+import retrofit2.Retrofit
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+
+@OptIn(ExperimentalSerializationApi::class)
+class KotlinxSerializationJsonConverterFactoryContextualListTest {
+ @get:Rule
+ val server = MockWebServer()
+
+ private lateinit var service: Service
+
+ interface Service {
+ @GET("/")
+ fun deserialize(): Call>
+
+ @POST("/")
+ fun serialize(@Body users: List): Call
+ }
+
+ data class User(val name: String)
+
+ object UserSerializer : KSerializer {
+ override val descriptor = PrimitiveSerialDescriptor("User", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): User =
+ decoder.decodeSerializableValue(UserResponse.serializer()).run {
+ User(name)
+ }
+
+ override fun serialize(encoder: Encoder, value: User): Unit =
+ encoder.encodeSerializableValue(UserResponse.serializer(), UserResponse(value.name))
+
+ @Serializable
+ private data class UserResponse(val name: String)
+ }
+
+ private val json = Json {
+ serializersModule = SerializersModule {
+ contextual(UserSerializer)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ val retrofit = Retrofit.Builder()
+ .baseUrl(server.url("/"))
+ .addConverterFactory(json.asConverterFactory())
+ .build()
+ service = retrofit.create(Service::class.java)
+ }
+
+ @Test
+ fun deserialize() {
+ server.enqueue(MockResponse().setBody("""[{"name":"Bob"}]"""))
+ val user = service.deserialize().execute().body()!!
+ Assert.assertEquals(listOf(User("Bob")), user)
+ }
+
+ @Test
+ fun serialize() {
+ server.enqueue(MockResponse())
+ service.serialize(listOf(User("Bob"))).execute()
+ val request = server.takeRequest()
+ Assert.assertEquals("""[{"name":"Bob"}]""", request.body.readUtf8())
+ Assert.assertEquals("application/json; charset=UTF-8", request.headers["Content-Type"])
+ }
+}
diff --git a/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryContextualTest.kt b/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryContextualTest.kt
new file mode 100644
index 0000000000..be0ce19613
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryContextualTest.kt
@@ -0,0 +1,87 @@
+package retrofit2.converter.kotlinx.serialization.json
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.contextual
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import retrofit2.Call
+import retrofit2.Retrofit
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+
+@OptIn(ExperimentalSerializationApi::class)
+class KotlinxSerializationJsonConverterFactoryContextualTest {
+ @get:Rule
+ val server = MockWebServer()
+
+ private lateinit var service: Service
+
+ interface Service {
+ @GET("/")
+ fun deserialize(): Call
+
+ @POST("/")
+ fun serialize(@Body user: User): Call
+ }
+
+ data class User(val name: String)
+
+ object UserSerializer : KSerializer {
+ override val descriptor = PrimitiveSerialDescriptor("User", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): User =
+ decoder.decodeSerializableValue(UserResponse.serializer()).run {
+ User(name)
+ }
+
+ override fun serialize(encoder: Encoder, value: User): Unit =
+ encoder.encodeSerializableValue(UserResponse.serializer(), UserResponse(value.name))
+
+ @Serializable
+ private data class UserResponse(val name: String)
+ }
+
+ private val json = Json {
+ serializersModule = SerializersModule {
+ contextual(UserSerializer)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ val retrofit = Retrofit.Builder()
+ .baseUrl(server.url("/"))
+ .addConverterFactory(json.asConverterFactory())
+ .build()
+ service = retrofit.create(Service::class.java)
+ }
+
+ @Test
+ fun deserialize() {
+ server.enqueue(MockResponse().setBody("""{"name":"Bob"}"""))
+ val user = service.deserialize().execute().body()!!
+ assertEquals(User("Bob"), user)
+ }
+
+ @Test
+ fun serialize() {
+ server.enqueue(MockResponse())
+ service.serialize(User("Bob")).execute()
+ val request = server.takeRequest()
+ assertEquals("""{"name":"Bob"}""", request.body.readUtf8())
+ assertEquals("application/json; charset=UTF-8", request.headers["Content-Type"])
+ }
+}
diff --git a/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryTest.kt b/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryTest.kt
new file mode 100644
index 0000000000..d76205b8cc
--- /dev/null
+++ b/retrofit-converters/kotlinx-serialization-json/src/test/java/retrofit2/converter/kotlinx/serialization/json/KotlinxSerializationJsonConverterFactoryTest.kt
@@ -0,0 +1,53 @@
+package retrofit2.converter.kotlinx.serialization.json
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import retrofit2.Call
+import retrofit2.Retrofit
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+
+@OptIn(ExperimentalSerializationApi::class)
+class KotlinxSerializationJsonConverterFactoryTest {
+ @get:Rule val server = MockWebServer()
+
+ private lateinit var service: Service
+
+ interface Service {
+ @GET("/") fun deserialize(): Call
+ @POST("/") fun serialize(@Body user: User): Call
+ }
+
+ @Serializable
+ data class User(val name: String)
+
+ @Before fun setUp() {
+ val retrofit = Retrofit.Builder()
+ .baseUrl(server.url("/"))
+ .addConverterFactory(Json.asConverterFactory())
+ .build()
+ service = retrofit.create(Service::class.java)
+ }
+
+ @Test fun deserialize() {
+ server.enqueue(MockResponse().setBody("""{"name":"Bob"}"""))
+ val user = service.deserialize().execute().body()!!
+ assertEquals(User("Bob"), user)
+ }
+
+ @Test fun serialize() {
+ server.enqueue(MockResponse())
+ service.serialize(User("Bob")).execute()
+ val request = server.takeRequest()
+ assertEquals("""{"name":"Bob"}""", request.body.readUtf8())
+ assertEquals("application/json; charset=UTF-8", request.headers["Content-Type"])
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index db95ce9b0c..20fc9aeb1e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -38,6 +38,7 @@ include ':retrofit-converters:java8'
include ':retrofit-converters:jaxb'
include ':retrofit-converters:jaxb3'
include ':retrofit-converters:kotlinx-serialization'
+include ':retrofit-converters:kotlinx-serialization-json'
include ':retrofit-converters:moshi'
include ':retrofit-converters:protobuf'
include ':retrofit-converters:scalars'