Ktor logo

Ktor

Building Asynchronous Servers and Clients in Kotlin

Ktor 2.0 Released

Read this post in other languages:

We’re extremely excited to announce the release of Ktor 2.0. We have been working on this version for approximately one year and happy to finally get it out the door! 

Ktor 2.0 introduces many new features, but a major version which means that there are breaking changes, it has allowed us the opportunity to perform some maintenance work and get rid of legacy decisions. Don’t worry though – despite the breaking changes, we’ve tried our best to minimize these and also aid with some tooling that hopefully can help with automated migrations. More on that later though, first let’s take a look at some of the new functionality

Ktor Server

Simplified Extensibility

We’ve always built Ktor in mind for extensibility. Initially called Features, and now renamed to Plugins, all of the features (now you see why we renamed it) that Ktor offers are built using plugin architecture. The problem we encountered though was that for some folks it was hard to understand the architecture model behind it. In 2.0 we drastically simplified the extensibility API making the creation of plugins significantly easier. 

Take a look at the following API code from 1.x

companion object Feature : ApplicationFeature<ApplicationCallPipeline, CustomHeader.Configuration, CustomHeader> {
    override val key = AttributeKey<CustomHeader>("CustomPlugin")
    override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): CustomHeader {
       val configuration = Configuration().apply(configure)

       val feature = CustomHeader(configuration)

       pipeline.intercept(ApplicationCallPipeline.Call) {

            feature.intercept(this)
        }

       return feature
    }
}

The equivalent in 2.0 is

val myCustomPlugin = createApplicationPlugin("CustomPlugin") {
    onCall {

    }

    onCallReceive {

    }

    onCallRespond {

    }
}

Most of the existing plugins have been converted to use the new API and we’re pretty sure that the majority of scenarios can be covered. For more details, checkout the conversion for the `CustomHeader` plugin from the old to the new API, as well as the documentation for plugin development.

We have a lot more planned when it comes to extensibility, including facilities to easily publish and consume plugins from a marketplace! 

Native support

On the server side, we now support Kotlin/Native in addition to GraalVM (which was already supported from 1.6), meaning that you have two options when it comes to self-contained server applications. 

In regards to Kotin/Native support, currently it is limited to using CIO as an engine and we are continuing to work in areas of performance. We do recommend however using the new Kotlin/Native memory model. 

Other server improvements

We also have a series of smaller improvements for Ktor server including random port support

fun main() {
    embeddedServer(Netty, port = 0) {
        configureRouting()
    }.start(wait = true)
}

as well as improved testing API, type-safe routing, XML Serialization, subroutes for plugins, and 60+ bug fixes and other features

Ktor Client

Simplified APIs

Continuing with the simplification of APIs, in the case of Ktor client, we’ve introduced new APIs to deal with common HTTP requests

val result = client.post("http://127.0.0.1:$port/") {

}
result.bodyAsText()

We’ve gotten rid of the generic post<T>, get<T> methods. Everything now returns an `HttpResponse` where you can access the body (with `bodyAsText`, `bodyAsChannel`) as well as the headers.

Retries

We now have built-in support for retries, including adjustment of time between retries

val client = HttpClient(CIO) {
    install(HttpRequestRetry) {
        maxRetries = 5
        retryIf { request, response ->
            !response.status.isSuccess()
        }
        retryOnExceptionIf { _, cause ->
            cause is NetworkError
        }
        delayMillis { retry ->
            retry * 3000L
        } // retries in 3, 6, 9, etc. seconds
    }
}

Content Negotiation

If you’ve been using the content negotiation feature in the server, you’re probably already familiar with what it is and how it works. Essentially it’s the ability for a client and server to negotiate the different types of content that can be requested and served. The negotiation aspect of it was only available up to now for the server. We’ve now brought this to the client too!

val client = HttpClient(CIO) {
    install(ContentNegotiation) {
    }
}

This plugin effectively replaced the `JsonFeature`. 

Other client improvements

In addition to the above, the client also includes shortcut APIs for authentication such as `basic()` and `bearer()` helper functions, as well as interceptors at request level, a new metrics plugin, XML serialization, and many bug fixes and other features.

Migrating to Ktor 2.0

An issue we’ve had with Ktor has been the inconsistency of module and package names. Some had the prefix server, while others did not. Some worked for server and client, while not having any prefix whatsoever. In addition, we had multiple plugins in a single module, making discovery cumbersome. 

The change to a major version has provided us the opportunity to introduce some consistent naming conventions when it comes to modules and packages. Moving forward, anything that is only server, will have the server prefix. Anything that is client, will have the client prefix. Common packages will have the no prefix. In addition, plugins are now contained in their own modules. 

import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.content.*
import io.ktor.util.*
import io.ktor.utils.io.*

And the same goes for module names 

implementation "io.ktor:ktor-server-auth:$ktor_version"
implementation "io.ktor:ktor-server-default-headers:$ktor_version"
implementation "io.ktor:ktor-server-sessions:$ktor_version"
implementation "io.ktor:ktor-server-status-pages:$ktor_version"
implementation "io.ktor:ktor-server-cors:$ktor_version"
implementation "io.ktor:ktor-server-conditional-headers:$ktor_version"

To help with migration, we’re providing folks not only mapping documentation but also a migration tool with IntelliJ IDEA which attempts to migrate your Ktor project automatically (please note that Kotlin Multiplatform Projects are currently not supported)

Kotlin 1.6.20 support and up to date documentation

For folks using the latest release of Kotlin, know that Ktor 2.0 is compatible with 1.6.20. We’ve also continued to work on the documentation for Ktor, bringing it up to speed with all the changes. Moving forward we’re working on revamping the API documentation also. 

For a complete list of everything in 2.0, check out the change log. And create your first Ktor 2.0 today!