This guide provides instructions on how to migrate your Ktor application from the 1.6.x version to 2.0.0.
To unify and better distinguish the server and client APIs, server code is moved to the io.ktor.server.*
package (KTOR-2865).
This means that you need to update dependencies for and imports in your application, as shown below.
Subsystem | 1.6.x | 2.0.0 |
---|---|---|
Locations | io.ktor:ktor-locations |
io.ktor:ktor-server-locations |
Webjars | io.ktor:ktor-webjars |
io.ktor:ktor-server-webjars |
AutoHeadResponse | io.ktor:ktor-server-core |
io.ktor:ktor-server-auto-head-response |
StatusPages | io.ktor:ktor-server-core |
io.ktor:ktor-server-status-pages |
CallId | io.ktor:ktor-server-core |
io.ktor:ktor-server-call-id |
DoubleReceive | io.ktor:ktor-server-core |
io.ktor:ktor-server-double-receive |
HTML DSL | io.ktor:ktor-html-builder |
io.ktor:ktor-server-html-builder |
FreeMarker | io.ktor:ktor-freemarker |
io.ktor:ktor-server-freemarker |
Velocity | io.ktor:ktor-velocity |
io.ktor:ktor-server-velocity |
Mustache | io.ktor:ktor-mustache |
io.ktor:ktor-server-mustache |
Thymeleaf | io.ktor:ktor-thymeleaf |
io.ktor:ktor-server-thymeleaf |
Pebble | io.ktor:ktor-pebble |
io.ktor:ktor-server-pebble |
kotlinx.serialization | io.ktor:ktor-serialization |
io.ktor:ktor-server-content-negotiation , io.ktor:ktor-serialization-kotlinx-json |
Gson | io.ktor:ktor-gson |
io.ktor:ktor-server-content-negotiation , io.ktor:ktor-serialization-gson |
Jackson | io.ktor:ktor-jackson |
io.ktor:ktor-server-content-negotiation , io.ktor:ktor-serialization-jackson |
Authentication | io.ktor:ktor-auth |
io.ktor:ktor-server-auth |
JWT authentication | io.ktor:ktor-auth-jwt |
io.ktor:ktor-server-auth-jwt |
LDAP authentication | io.ktor:ktor-auth-ldap |
io.ktor:ktor-server-auth-ldap |
DataConversion | io.ktor:ktor-server-core |
io.ktor:ktor-server-data-conversion |
DefaultHeaders | io.ktor:ktor-server-core |
io.ktor:ktor-server-default-headers |
Compression | io.ktor:ktor-server-core |
io.ktor:ktor-server-compression |
CachingHeaders | io.ktor:ktor-server-core |
io.ktor:ktor-server-caching-headers |
ConditionalHeaders | io.ktor:ktor-server-core |
io.ktor:ktor-server-conditional-headers |
CORS | io.ktor:ktor-server-core |
io.ktor:ktor-server-cors |
Forwarded headers | io.ktor:ktor-server-core |
io.ktor:ktor-server-forwarded-header |
HSTS | io.ktor:ktor-server-core |
io.ktor:ktor-server-hsts |
HttpsRedirect | io.ktor:ktor-server-core |
io.ktor:ktor-server-http-redirect |
PartialContent | io.ktor:ktor-server-core |
io.ktor:ktor-server-partial-content |
WebSockets | io.ktor:ktor-websockets |
io.ktor:ktor-server-websockets |
CallLogging | io.ktor:ktor-server-core |
io.ktor:ktor-server-call-logging |
Micrometer metric | io.ktor:ktor-metrics-micrometer |
io.ktor:ktor-server-metrics-micrometer |
Dropwizard metrics | io.ktor:ktor-metrics |
io.ktor:ktor-server-metrics |
To add all plugins at once, you can use the
io.ktor:ktor-server
artifact.
Subsystem | 1.6.x | 2.0.0 |
---|---|---|
Application | import io.ktor.application.* |
import io.ktor.server.application.* |
Configuration | import io.ktor.config.* |
import io.ktor.server.config.* |
Routing | import io.ktor.routing.* |
import io.ktor.server.routing.* |
AutoHeadResponse | import io.ktor.features.* |
import io.ktor.server.plugins.autohead.* |
StatusPages | import io.ktor.features.* |
import io.ktor.server.plugins.statuspages.* |
CallId | import io.ktor.features.* |
import io.ktor.server.plugins.callid.* |
DoubleReceive | import io.ktor.features.* |
import io.ktor.server.plugins.doublereceive.* |
Requests | import io.ktor.request.* |
import io.ktor.server.request.* |
Responses | import io.ktor.response.* |
import io.ktor.server.response.* |
Plugins | import io.ktor.features.* |
import io.ktor.server.plugins.* |
Locations | import io.ktor.locations.* |
import io.ktor.server.locations.* |
Static content | import io.ktor.http.content.* |
import io.ktor.server.http.content.* |
HTML DSL | import io.ktor.html.* |
import io.ktor.server.html.* |
FreeMarker | import io.ktor.freemarker.* |
import io.ktor.server.freemarker.* |
Velocity | import io.ktor.velocity.* |
import io.ktor.server.velocity.* |
Mustache | import io.ktor.mustache.* |
import io.ktor.server.mustache.* |
Thymeleaf | import io.ktor.thymeleaf.* |
import io.ktor.server.thymeleaf.* |
Pebble | import io.ktor.pebble.* |
import io.ktor.server.pebble.* |
ContentNegotiation | import io.ktor.features.* |
import io.ktor.server.plugins.contentnegotiation.* |
kotlinx.serialization | import io.ktor.serialization.* |
import io.ktor.serialization.kotlinx.json.* |
Gson | import io.ktor.gson.* |
import io.ktor.serialization.gson.* |
Jackson | import io.ktor.jackson.* |
import io.ktor.serialization.jackson.* |
Authentication | import io.ktor.auth.* |
import io.ktor.server.auth.* |
JWT authentication | import io.ktor.auth.jwt.* |
import io.ktor.server.auth.jwt.* |
LDAP authentication | import io.ktor.auth.ldap.* |
import io.ktor.server.auth.ldap.* |
Sessions | import io.ktor.sessions.* |
import io.ktor.server.sessions.* |
DefaultHeaders | import io.ktor.features.* |
import io.ktor.server.plugins.defaultheaders.* |
Compression | import io.ktor.features.* |
import io.ktor.server.plugins.compression.* |
CachingHeaders | import io.ktor.features.* |
import io.ktor.server.plugins.cachingheaders.* |
ConditionalHeaders | import io.ktor.features.* |
import io.ktor.server.plugins.conditionalheaders.* |
CORS | import io.ktor.features.* |
import io.ktor.server.plugins.cors.* |
Forwarded headers | import io.ktor.features.* |
import io.ktor.server.plugins.forwardedheaders.* |
HSTS | import io.ktor.features.* |
import io.ktor.server.plugins.hsts.* |
HttpsRedirect | import io.ktor.features.* |
import io.ktor.server.plugins.httpsredirect.* |
PartialContent | import io.ktor.features.* |
import io.ktor.server.plugins.partialcontent.* |
WebSockets | import io.ktor.websocket.* |
import io.ktor.server.websocket.* |
CallLogging | import io.ktor.features.* |
import io.ktor.server.plugins.callloging.* |
Micrometer metric | import io.ktor.metrics.micrometer.* |
import io.ktor.server.metrics.micrometer.* |
Dropwizard metrics | import io.ktor.metrics.dropwizard.* |
import io.ktor.server.metrics.dropwizard.* |
WebSockets code is moved from http-cio
to the websockets
package. This requires updating imports as follows:
1.6.x | 2.0.0 |
---|---|
import io.ktor.http.cio.websocket.* |
import io.ktor.websocket.* |
Note that this change also affects the client.
In Ktor 2.0.0, Feature is renamed to Plugin to better describe functionality that intercepts the request/response pipeline (KTOR-2326). This affects the entire Ktor API and requires updating your application as described below.
Installing any plugin requires updating imports and also depends on moving server code to the io.ktor.server.*
package:
1.6.x | 2.0.0 |
---|---|
import io.ktor.features.* |
import io.ktor.server.plugins.* |
Renaming Feature to Plugin introduces the following changes for API related to custom plugins:
- The
ApplicationFeature
interface is renamed toBaseApplicationPlugin
. - The
Features
pipeline phase is renamed toPlugins
.
Note that starting with v2.0.0, Ktor provides the new API for creating custom plugins.
Content negotiation and serialization server API was refactored to reuse serialization libraries between the server and client. The main changes are:
ContentNegotiation
is moved fromktor-server-core
to a separatektor-server-content-negotiation
artifact.- Serialization libraries are moved from
ktor-*
to thektor-serialization-*
artifacts also used by the client.
You need to update dependencies for and imports in your application, as shown below.
Subsystem | 1.6.x | 2.0.0 |
---|---|---|
ContentNegotiation | io.ktor:ktor-server-core |
io.ktor:ktor-server-content-negotiation |
kotlinx.serialization | io.ktor:ktor-serialization |
io.ktor:ktor-serialization-kotlinx-json |
Gson | io.ktor:ktor-gson |
io.ktor:ktor-serialization-gson |
Jackson | io.ktor:ktor-jackson |
io.ktor:ktor-serialization-jackson |
Subsystem | 1.6.x | 2.0.0 |
---|---|---|
kotlinx.serialization | import io.ktor.serialization.* |
import io.ktor.serialization.kotlinx.json.* |
Gson | import io.ktor.gson.* |
import io.ktor.serialization.gson.* |
Jackson | import io.ktor.jackson.* |
import io.ktor.serialization.jackson.* |
Signatures of functions exposed by the ContentConverter interface are changed in the following way:
interface ContentConverter {
suspend fun convertForSend(context: PipelineContext<Any, ApplicationCall>, contentType: ContentType, value: Any): Any?
suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any?
}
interface ContentConverter {
suspend fun serialize(contentType: ContentType, charset: Charset, typeInfo: TypeInfo, value: Any): OutgoingContent?
suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any?
}
With v2.0.0, the Ktor server uses a new API for testing, which solves various issues described in KTOR-971. The main changes are:
- The
withTestApplication
/withApplication
functions are replaced with a newtestApplication
function. - Inside the
testApplication
function, you need to use the existing Ktor client instance to make requests to your server and verify the results. - To test specific functionalities (for example, cookies or WebSockets), you need to create a new client instance and install a corresponding plugin.
Let's take a look at several examples of migrating 1.6.x tests to 2.0.0:
In the test below, the handleRequest
function is replaced with the client.get
request:
{src="snippets/engine-main/src/test/kotlin/EngineMainTest.kt" lines="18-26"}
{src="snippets/engine-main/src/test/kotlin/EngineMainTest.kt" lines="11-16"}
In the test below, the handleRequest
function is replaced with the client.post
request:
{src="snippets/post-form-parameters/src/test/kotlin/ApplicationTest.kt" lines="20-28"}
{src="snippets/post-form-parameters/src/test/kotlin/ApplicationTest.kt" lines="11-18"}
To build multipart/form-data
in v2.0.0, you need to pass MultiPartFormDataContent
to the client's setBody
function:
{src="snippets/upload-file/src/test/kotlin/UploadFileTest.kt" lines="36-61"}
{src="snippets/upload-file/src/test/kotlin/UploadFileTest.kt" lines="15-34"}
In v.1.6.x, you can serialize JSON data using the Json.encodeToString
function provided by the kotlinx.serialization
library.
With v2.0.0, you need to create a new client instance and install the ContentNegotiation plugin that allows serializing/deserializing the content in a specific format:
{src="snippets/json-kotlinx/src/test/kotlin/ApplicationTest.kt" lines="46-55"}
{src="snippets/json-kotlinx/src/test/kotlin/ApplicationTest.kt" lines="31-44"}
In v1.6.x, cookiesSession
is used to preserve cookies between requests when testing. With v2.0.0, you need to create a new client instance and install the HttpCookies plugin:
{src="snippets/session-cookie-client/src/test/kotlin/ApplicationTest.kt" lines="29-46"}
{src="snippets/session-cookie-client/src/test/kotlin/ApplicationTest.kt" lines="12-27"}
In the old API, handleWebSocketConversation
is used to test WebSocket conversations. With v2.0.0, you can test WebSocket conversations by using the WebSockets plugin provided by the client:
{src="snippets/server-websockets/src/test/kotlin/com/example/ModuleTest.kt" lines="28-40"}
{src="snippets/server-websockets/src/test/kotlin/com/example/ModuleTest.kt" lines="10-26"}
With v2.0.0, the DoubleReceive plugin configuration introduces the cacheRawRequest
property, which is opposite to receiveEntireContent
:
- In v1.6.x, the
receiveEntireContent
property is set tofalse
by default. - In v2.0.0,
cacheRawRequest
is set totrue
by default. ThereceiveEntireContent
property is removed.
In v2.0.0, the ForwardedHeaderSupport
and XForwardedHeaderSupport
plugins are renamed to ForwardedHeaders and XForwardedHeaders
, respectively.
The options function used to define caching options now accepts the ApplicationCall
as a lambda argument in addition to OutgoingContent
:
install(CachingHeaders) {
options { outgoingContent ->
// ...
}
}
install(CachingHeaders) {
options { call, outgoingContent ->
// ...
}
}
The version function used to define a list of resource versions now accepts the ApplicationCall
as a lambda argument in addition to OutgoingContent
:
install(ConditionalHeaders) {
version { outgoingContent ->
// ...
}
}
install(ConditionalHeaders) {
version { call, outgoingContent ->
// ...
}
}
Several functions used in CORS configuration are renamed:
host
->allowHost
header
->allowHeader
method
->allowMethod
install(CORS) {
host("0.0.0.0:5000")
header(HttpHeaders.ContentType)
method(HttpMethod.Options)
}
install(CORS) {
allowHost("0.0.0.0:5000")
allowHeader(HttpHeaders.ContentType)
allowMethod(HttpMethod.Options)
}
In v1.6.x, the baseName
property is used to specify the base name (prefix) of Ktor metrics used for monitoring HTTP requests.
By default, it equals to ktor.http.server
.
With v2.0.0, baseName
is replaced with metricName
whose default value is ktor.http.server.requests
.
In v2.0.0, API used to make requests and receive responses is updated to make it more consistent and discoverable (KTOR-29).
Request functions with multiple parameters are deprecated. For example, the port
and path
parameters need to be replaced with a the url
parameter exposed by HttpRequestBuilder:
client.get(port = 8080, path = "/customer/3")
client.get { url(port = 8080, path = "/customer/3") }
The HttpRequestBuilder
also allows you to specify additional request parameters inside the request function lambda.
The HttpRequestBuilder.body
property used to set the request body is replaced with the HttpRequestBuilder.setBody
function:
client.post("http://localhost:8080/post") {
body = "Body content"
}
client.post("http://localhost:8080/post") {
setBody("Body content")
}
With v2.0.0, request functions (such as get
, post
, put
, submitForm, and so on) don't accept generic arguments for receiving an object of a specific type.
Now all request functions return a HttpResponse
object, which exposes the body
function with a generic argument for receiving a specific type instance.
You can also use bodyAsText
or bodyAsChannel
to receive content as a string or channel.
val httpResponse: HttpResponse = client.get("https://ktor.io/")
val stringBody: String = httpResponse.receive()
val byteArrayBody: ByteArray = httpResponse.receive()
val httpResponse: HttpResponse = client.get("https://ktor.io/")
val stringBody: String = httpResponse.body()
val byteArrayBody: ByteArray = httpResponse.body()
With the ContentNegotiation plugin installed, you can receive an arbitrary object as follows:
val customer: Customer = client.get("http://localhost:8080/customer/3")
val customer: Customer = client.get("http://localhost:8080/customer/3").body()
Due to removing generic arguments from request functions, receiving a streaming response requires separate functions.
To achieve this, functions with the prepare
prefix are added, such as prepareGet
or preparePost
:
public suspend fun HttpClient.prepareGet(builder: HttpRequestBuilder): HttpStatement
public suspend fun HttpClient.preparePost(builder: HttpRequestBuilder): HttpStatement
The example below shows how to change your code in this case:
client.get<HttpStatement>("https://ktor.io/").execute { httpResponse ->
val channel: ByteReadChannel = httpResponse.receive()
while (!channel.isClosedForRead) {
// Read data
}
}
client.prepareGet("https://ktor.io/").execute { httpResponse ->
val channel: ByteReadChannel = httpResponse.body()
while (!channel.isClosedForRead) {
// Read data
}
}
You can find the full example here: .
With v2.0.0, the expectSuccess
property used for response validation is set to false
by default.
This requires the following changes in your code:
- To enable default validation and throw exceptions for non-2xx responses, set the
expectSuccess
property totrue
. - If you handle non-2xx exceptions using
handleResponseException
, you also need to enableexpectSuccess
explicitly.
The Ktor client now supports content negotiation and shares serialization libraries with the Ktor server. The main changes are:
JsonFeature
is deprecated in favor ofContentNegotiation
, which can be found in thektor-client-content-negotiation
artifact.- Serialization libraries are moved from
ktor-client-*
to thektor-serialization-*
artifacts.
You need to update dependencies for and imports in your client code, as shown below.
Subsystem | 1.6.x | 2.0.0 |
---|---|---|
ContentNegotiation |
n/a | io.ktor:ktor-client-content-negotiation |
kotlinx.serialization | io.ktor:ktor-client-serialization |
io.ktor:ktor-serialization-kotlinx-json |
Gson | io.ktor:ktor-client-gson |
io.ktor:ktor-serialization-gson |
Jackson | io.ktor:ktor-client-jackson |
io.ktor:ktor-serialization-jackson |
Subsystem | 1.6.x | 2.0.0 |
---|---|---|
ContentNegotiation |
n/a | import io.ktor.client.plugins.contentnegotiation.* |
kotlinx.serialization | import io.ktor.client.features.json.* |
import io.ktor.serialization.kotlinx.json.* |
Gson | import io.ktor.client.features.json.* |
import io.ktor.serialization.gson.* |
Jackson | import io.ktor.client.features.json.* |
import io.ktor.serialization.jackson.* |
The refreshTokens function now uses the RefreshTokenParams
instance as lambda receiver (this
) instead of the HttpResponse
lambda argument (it
):
bearer {
refreshTokens { // it: HttpResponse
// ...
}
}
bearer {
refreshTokens { // this: RefreshTokenParams
// ...
}
}
RefreshTokenParams
exposes the following properties:
response
to access response parameters;client
to make a request to refresh tokens;oldTokens
to access tokens obtained usingloadTokens
.
The API of the HttpSend plugin is changed as follows:
client[HttpSend].intercept { originalCall, request ->
if (originalCall.something()) {
val newCall = execute(request)
// ...
}
}
client.plugin(HttpSend).intercept { request ->
val originalCall = execute(request)
if (originalCall.something()) {
val newCall = execute(request)
// ...
}
}
Note that with v2.0.0 indexed access is not available for accessing plugins. Use the HttpClient.plugin function instead.
With the 2.0.0 version, the HttpClient.get
function accepting a client plugin is removed. Use the HttpClient.plugin
function instead.
client.get(HttpSend).intercept { ... }
// or
client[HttpSend].intercept { ... }
client.plugin(HttpSend).intercept { ... }
As for the Ktor server, Feature is renamed to Plugin in the client API. This might affect your application, as described below.
Update imports for installing plugins:
Subsystem | 1.6.x | 2.0.0 |
|
import io.ktor.client.features.* |
import io.ktor.client.plugins.* |
Authentication |
import io.ktor.client.features.auth.*
import io.ktor.client.features.auth.providers.*
|
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
|
Cookies | import io.ktor.client.features.cookies.* |
import io.ktor.client.plugins.cookies.* |
Logging | import io.ktor.client.features.logging.* |
import io.ktor.client.plugins.logging.* |
WebSockets | import io.ktor.client.features.websocket.* |
import io.ktor.client.plugins.websocket.* |
Content encoding | import io.ktor.client.features.compression.* |
import io.ktor.client.plugins.compression.* |
The HttpClientFeature
interface is renamed to HttpClientPlugin
.
Given that the Ios
engine targets not only iOS but other operating systems, including macOS, or tvOS, in v2.0.0, it is renamed to Darwin
. This causes the following changes:
- The
io.ktor:ktor-client-ios
artifact is renamed toio.ktor:ktor-client-darwin
. - To create the
HttpClient
instance, you need to pass theDarwin
class as an argument. - The
IosClientEngineConfig
configuration class is renamed toDarwinClientEngineConfig
.
To learn how to configure the Darwin
engine, see the section.
WebSockets code is moved from http-cio
to the websockets
package. This requires updating imports as follows:
1.6.x | 2.0.0 |
---|---|
import io.ktor.http.cio.websocket.* |
import io.ktor.websocket.* |