Ktor 3.0.0 Help

Migrating from 1.6.x to 2.0.x

This guide provides instructions on how to migrate your Ktor application from the 1.6.x version to 2.0.x.

Ktor Server

Server code is moved to the 'io.ktor.server.*' package

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.

Dependencies

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

Sessions

io.ktor:ktor-server-core

io.ktor:ktor-server-sessions

Imports

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 to the 'websockets' package

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.

Feature is renamed to Plugin

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.

Imports

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.*

Custom plugins

Renaming Feature to Plugin introduces the following changes for API related to custom plugins:

  • The ApplicationFeature interface is renamed to BaseApplicationPlugin.

  • The Features pipeline phase is renamed to Plugins.

Content negotiation and serialization

Content negotiation and serialization server API was refactored to reuse serialization libraries between the server and client. The main changes are:

  • ContentNegotiation is moved from ktor-server-core to a separate ktor-server-content-negotiation artifact.

  • Serialization libraries are moved from ktor-* to the ktor-serialization-* artifacts also used by the client.

You need to update dependencies for and imports in your application, as shown below.

Dependencies

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

Imports

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.*

Custom converters

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? }

Testing API

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 new testApplication 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:

Basic server test

In the test below, the handleRequest function is replaced with the client.get request:

@Test fun testRootLegacyApi() { withTestApplication(Application::module) { handleRequest(HttpMethod.Get, "/").apply { assertEquals(HttpStatusCode.OK, response.status()) assertEquals("Hello, world!", response.content) } } }
@Test fun testRoot() = testApplication { val response = client.get("/") assertEquals(HttpStatusCode.OK, response.status) assertEquals("Hello, world!", response.bodyAsText()) }

x-www-form-urlencoded

In the test below, the handleRequest function is replaced with the client.post request:

@Test fun testPostLegacyApi() = withTestApplication(Application::main) { with(handleRequest(HttpMethod.Post, "/signup"){ addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) setBody(listOf("username" to "JetBrains", "email" to "example@jetbrains.com", "password" to "foobar", "confirmation" to "foobar").formUrlEncode()) }) { assertEquals("The 'JetBrains' account is created", response.content) } }
@Test fun testPost() = testApplication { val response = client.post("/signup") { header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) setBody(listOf("username" to "JetBrains", "email" to "example@jetbrains.com", "password" to "foobar", "confirmation" to "foobar").formUrlEncode()) } assertEquals("The 'JetBrains' account is created", response.bodyAsText()) }

multipart/form-data

To build multipart/form-data in v2.0.0, you need to pass MultiPartFormDataContent to the client's setBody function:

@Test fun testUploadLegacyApi() = withTestApplication(Application::main) { with(handleRequest(HttpMethod.Post, "/upload"){ val boundary = "WebAppBoundary" val fileBytes = File("ktor_logo.png").readBytes() addHeader(HttpHeaders.ContentType, ContentType.MultiPart.FormData.withParameter("boundary", boundary).toString()) setBody(boundary, listOf( PartData.FormItem("Ktor logo", { }, headersOf( HttpHeaders.ContentDisposition, ContentDisposition.Inline .withParameter(ContentDisposition.Parameters.Name, "description") .toString() )), //TODO: document this change PartData.FileItem({ ByteReadChannel(fileBytes) }, {}, headersOf( HttpHeaders.ContentDisposition, ContentDisposition.File .withParameter(ContentDisposition.Parameters.Name, "image") .withParameter(ContentDisposition.Parameters.FileName, "ktor_logo.png") .toString() )) )) }) { assertEquals("Ktor logo is uploaded to 'uploads/ktor_logo.png'", response.content)
class ApplicationTest { @Test fun testUpload() = testApplication { val boundary = "WebAppBoundary" val response = client.post("/upload") { setBody( MultiPartFormDataContent( formData { append("description", "Ktor logo") append("image", File("ktor_logo.png").readBytes(), Headers.build { append(HttpHeaders.ContentType, "image/png") append(HttpHeaders.ContentDisposition, "filename=\"ktor_logo.png\"") }) }, boundary, ContentType.MultiPart.FormData.withParameter("boundary", boundary) ) ) } assertEquals("Ktor logo is uploaded to 'uploads/ktor_logo.png'", response.bodyAsText())

JSON data

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:

@Test fun testPostCustomerLegacyApi() = withTestApplication(Application::main) { with(handleRequest(HttpMethod.Post, "/customer"){ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString()) setBody(Json.encodeToString(Customer(3, "Jet", "Brains"))) }) { assertEquals("Customer stored correctly", response.content) assertEquals(HttpStatusCode.Created, response.status()) } }
@Test fun testPostCustomer() = testApplication { val client = createClient { install(ContentNegotiation) { json() } } val response = client.post("/customer") { contentType(ContentType.Application.Json) setBody(Customer(3, "Jet", "Brains")) } assertEquals("Customer stored correctly", response.bodyAsText()) assertEquals(HttpStatusCode.Created, response.status) }

Preserve cookies during testing

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:

@Test fun testRequestsLegacyApi() = withTestApplication(Application::main) { fun doRequestAndCheckResponse(path: String, expected: String) { handleRequest(HttpMethod.Get, path).apply { assertEquals(expected, response.content) } } cookiesSession { handleRequest(HttpMethod.Get, "/login") {}.apply {} doRequestAndCheckResponse("/user", "Session ID is 123abc. Reload count is 0.") doRequestAndCheckResponse("/user", "Session ID is 123abc. Reload count is 1.") doRequestAndCheckResponse("/user", "Session ID is 123abc. Reload count is 2.") handleRequest(HttpMethod.Get, "/logout").apply {} doRequestAndCheckResponse("/user", "Session doesn't exist or is expired.") } }
@Test fun testRequests() = testApplication { val client = createClient { install(HttpCookies) } val loginResponse = client.get("/login") val response1 = client.get("/user") assertEquals("Session ID is 123abc. Reload count is 1.", response1.bodyAsText()) val response2 = client.get("/user") assertEquals("Session ID is 123abc. Reload count is 2.", response2.bodyAsText()) val response3 = client.get("/user") assertEquals("Session ID is 123abc. Reload count is 3.", response3.bodyAsText()) val logoutResponse = client.get("/logout") assertEquals("Session doesn't exist or is expired.", logoutResponse.bodyAsText()) }

WebSockets

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:

@Test fun testConversationLegacyApi() { withTestApplication(Application::module) { handleWebSocketConversation("/echo") { incoming, outgoing -> val greetingText = (incoming.receive() as Frame.Text).readText() assertEquals("Please enter your name", greetingText) outgoing.send(Frame.Text("JetBrains")) val responseText = (incoming.receive() as Frame.Text).readText() assertEquals("Hi, JetBrains!", responseText) } } }
@Test fun testConversation() { testApplication { val client = createClient { install(WebSockets) } client.webSocket("/echo") { val greetingText = (incoming.receive() as? Frame.Text)?.readText() ?: "" assertEquals("Please enter your name", greetingText) send(Frame.Text("JetBrains")) val responseText = (incoming.receive() as Frame.Text).readText() assertEquals("Hi, JetBrains!", responseText) } } }

DoubleReceive

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 to false by default.

  • In v2.0.0, cacheRawRequest is set to true by default. The receiveEntireContent property is removed.

Forwarded headers

In v2.0.0, the ForwardedHeaderSupport and XForwardedHeaderSupport plugins are renamed to ForwardedHeaders and XForwardedHeaders, respectively.

Caching headers

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 -> // ... } }

Conditional headers

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 -> // ... } }

CORS

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) }

MicrometerMetrics

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.

Ktor Client

Requests and responses

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

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.

Request body

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") }

Responses

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()

Streaming responses

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: Streaming data.

Response validation

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:

HttpResponseValidator

The handleResponseException function is replaced with handleResponseExceptionWithRequest, which adds access to HttpRequest to provide additional information in exceptions:

HttpResponseValidator { handleResponseException { exception -> // ... } }
HttpResponseValidator { handleResponseExceptionWithRequest { exception, request -> // ... } }

Content negotiation and serialization

The Ktor client now supports content negotiation and shares serialization libraries with the Ktor server. The main changes are:

  • JsonFeature is deprecated in favor of ContentNegotiation, which can be found in the ktor-client-content-negotiation artifact.

  • Serialization libraries are moved from ktor-client-* to the ktor-serialization-* artifacts.

You need to update dependencies for and imports in your client code, as shown below.

Dependencies

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

Imports

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.*

Bearer authentication

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 using loadTokens.

HttpSend

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.

The HttpClient.get(plugin: HttpClientPlugin) function is removed

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 { ... }

Feature is renamed to Plugin

As for the Ktor server, Feature is renamed to Plugin in the client API. This might affect your application, as described below.

Imports

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.*

Custom plugins

The HttpClientFeature interface is renamed to HttpClientPlugin.

New memory model for Native targets

With v2.0.0, using the Ktor client on Native targets requires enabling the new Kotlin/Native memory model: Enable the new MM.

The 'Ios' engine is renamed to 'Darwin'

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 to io.ktor:ktor-client-darwin.

  • To create the HttpClient instance, you need to pass the Darwin class as an argument.

  • The IosClientEngineConfig configuration class is renamed to DarwinClientEngineConfig.

To learn how to configure the Darwin engine, see the Darwin section.

WebSockets code is moved to the 'websockets' package

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.*

Default request

The DefaultRequest plugin uses a DefaultRequestBuilder configuration class instead of HttpRequestBuilder:

val client = HttpClient(CIO) { defaultRequest { // this: HttpRequestBuilder } }
val client = HttpClient(CIO) { defaultRequest { // this: DefaultRequestBuilder } }
Last modified: 11 October 2023