Using Ktor Content Negotiation with kotlinx.serialization for Automatic JSON Binding
Configure Ktor ContentNegotiation with kotlinx.serialization to get automatic JSON conversion for request and response bodies.
07 Nov 2025, 16:24 UTC

Solution
Use Ktor's ContentNegotiation plugin with the kotlinx.serialization JSON converter to let the framework automatically turn Kotlin data classes into JSON responses and parse incoming JSON bodies into those classes.
How it works
The ContentNegotiation plugin inserts a converter pipeline into the application pipeline. For each request it inspects the Accept header to pick a response serializer and the Content-Type header to pick a request deserializer. When you register json() from ktor-serialization-kotlinx-json, the plugin uses Kotlinx Serialization's Json instance to serialize or deserialize objects annotated with @Serializable.
Configuration example
// Application.kt
import io.ktor.server.application.*
import io.ktor.server.contentnegotiation.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
fun Application.main() {
install(ContentNegotiation) {
json(Json { // configure Json as needed
ignoreUnknownKeys = true
prettyPrint = true
})
}
routing {
get("/user") {
val user = User(id = 42, name = "Ada")
call.respond(user) // automatic JSON serialization
}
post("/user") {
val user = call.receive() // automatic JSON deserialization
// process user...
call.respondText("Received ${user.name}", status = io.ktor.http.HttpStatusCode.OK)
}
}
}
@Serializable
data class User(val id: Int, val name: String)
To run the example:
- Create a new Ktor project (e.g., via
start.ktor.io) targeting Ktor 2.3.x. - Add dependencies in
build.gradle.kts:
dependencies {
implementation("io.ktor:ktor-server-content-negotiation:$ktor_version")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
Replace $ktor_version with the version you use (e.g., 2.3.2).
Start the server with ./gradlew run (requires JDK 11+). No special OS permissions are needed beyond the ability to bind to the chosen port (default 8080).
Check the result:
- Send a GET request:
curl -i http://localhost:8080/user. Expect a response headerContent-Type: application/jsonand a JSON body matching theUserinstance. - Send a POST request:
curl -i -X POST http://localhost:8080/user -H "Content-Type: application/json" -d '{"id":1,"name":"Test"}'. The server should reply withReceived Testand status 200.
Limits and common pitfalls
- Missing @Serializable: If the data class lacks the
@Serializableannotation, Kotlinx Serialization throws aSerializationExceptionat runtime. Verify by checking that the class is annotated. - Multiple converters for the same media type: Registering both
json()andgson()forapplication/jsonleads to the first registered converter being used, which can cause confusion. Keep only one converter per media type or explicitly order them. - Large payloads: The default JSON serializer reads the entire body into memory. For very large JSON documents consider enabling streaming (e.g., using
Json { isIgnoreUnknownKeys = true }with a customInput/Output) or limit request size viacall.receiveText(maxLength = …)before deserialization. - Version mismatch: Ensure the Ktor serialization plugin version matches the Ktor core version; mismatched versions can cause
NoSuchMethodError. Use the same$ktor_versionfor all Ktor artifacts.
Practical verification: after making a request, inspect the response headers with -i flag in curl; the presence of application/json confirms negotiation succeeded. If you receive a 500 error with a stacktrace mentioning SerializationException, check the @Serializable annotation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.