What it is
Ktor is JetBrains' asynchronous framework for HTTP servers and clients in Kotlin, built on coroutines with a plugin-based architecture.
Install plugins for content negotiation, authentication, logging and CORS, then declare routes in a nested DSL. Handlers are suspending functions.
Installation
implementation("io.ktor:ktor-server-netty:3.0.0")Getting started
The smallest useful thing you can do with it, and what each part means.
kotlin
fun main() {
embeddedServer(Netty, port = 8080) {
install(ContentNegotiation) { json() }
install(StatusPages) {
exception<NotFoundException> { call, _ ->
call.respond(HttpStatusCode.NotFound, ErrorResponse("not found"))
}
}
routing {
route("/books") {
get("{id}") {
val id = call.parameters["id"]?.toIntOrNull()
?: throw BadRequestException("id must be a number")
call.respond(store.find(id) ?: throw NotFoundException())
}
post {
val body = call.receive<CreateBook>()
call.respond(HttpStatusCode.Created, store.create(body))
}
}
}
}.start(wait = true)
}kotlin
val client = HttpClient(CIO) {
install(ContentNegotiation) { json() }
install(HttpTimeout) { requestTimeoutMillis = 10_000 }
install(HttpRequestRetry) { retryOnServerErrors(maxRetries = 3); exponentialDelay() }
}
val book: Book = client.get("https://api.example.com/books/42").body()
client.close() // or use it as a singleton for the app lifetimeAdvanced usage
Where the library earns its place over a simpler alternative.
kotlin
install(Authentication) {
jwt("auth-jwt") {
verifier(jwkProvider, issuer)
validate { credential ->
credential.payload.getClaim("sub").asString()
?.let { JWTPrincipal(credential.payload) }
}
challenge { _, _ -> call.respond(HttpStatusCode.Unauthorized) }
}
}
routing {
authenticate("auth-jwt") {
get("/me") {
val principal = call.principal<JWTPrincipal>()!!
call.respond(principal.payload.getClaim("sub").asString())
}
}
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Cannot transform this request's content
- ContentNegotiation is not installed, or the request lacks a Content-Type header the serialiser recognises.
- Routes return 404 unexpectedly
- Route nesting places the path under a parent prefix. Check the full concatenated path.
Best practices
- Use StatusPages for centralised error handling instead of per-route responses.
- Create one HttpClient and reuse it; constructing one per call is expensive.
- Group protected routes inside authenticate blocks so security is the default.
- Prefer Ktor for Multiplatform and Kotlin-first projects; Spring Boot has the larger ecosystem on the JVM.
Background
Why it exists, and what it was reacting to.
Ktor was designed to be Kotlin-first rather than a wrapper over a Java framework — routing is a DSL, everything is suspending, and it runs on JVM, Android and Kotlin/Native.
