Ktor 3.0.2 Help

Migrating from 2.2.x to 3.0.x

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

Ktor Server

ApplicationEngine, ApplicationEnvironment, and Application

Several design changes have been introduced to improve configurability and provide a more defined separation between the ApplicationEngine, ApplicationEnvironment and Application instances.

Before v3.0.0, ApplicationEngine managed ApplicationEnvironment, which in turn managed Application.

In the current design, however, Application is responsible for creating, owning, and initiating both ApplicationEngine and ApplicationEnvironment.

This restructuring comes with the following set of breaking changes:

These changes will impact existing code that relies on the previous model.

Renamed classes

Package

2.x.x

3.0.x

io.ktor:ktor-server-core

ApplicationEngineEnvironmentBuilder

ApplicationEnvironmentBuilder

io.ktor:ktor-server-core

applicationEngineEnvironment

applicationEnvironment

start() and stop() methods are removed from ApplicationEngineEnvironment

With the merge of AplicationEngineEnvironment to ApplicationEnvironment, the start() and stop() methods are now only accessible through ApplicationEngine.

2.x.x

3.0.x

ApplicationEngineEnvironment.start()

ApplicationEngine.start()

ApplicationEngineEnvironment.stop()

ApplicationEngine.stop()

Additionally, in the following table you can see the list of removed properties and their current corresponding ownership:

2.x.x

3.0.x

ApplicationEngineEnvironment.connectors

ApplicationEngine.Configuration.connectors

ApplicationEnvironment.developmentMode

Application.developmentMode

ApplicationEnvironment.monitor

Application.monitor

ApplicationEnvironment.parentCoroutineContext

Application.parentCoroutineContext

ApplicationEnvironment.rootPath

Application.rootPath

The ownership changes can be illustrated through the following example:

import io.ktor.server.application.* import io.ktor.server.cio.* import io.ktor.server.engine.* import org.slf4j.helpers.NOPLogger fun defaultServer(module: Application.() -> Unit) = embeddedServer(CIO, environment = applicationEngineEnvironment { log = NOPLogger.NOP_LOGGER connector { port = 8080 } module(module) } )
import io.ktor.server.application.* import io.ktor.server.cio.* import io.ktor.server.engine.* import org.slf4j.helpers.NOPLogger fun defaultServer(module: Application.() -> Unit) = embeddedServer(CIO, environment = applicationEnvironment { log = NOPLogger.NOP_LOGGER }, configure = { connector { port = 8080 } }, module )

Introduction of ServerConfigBuilder

A new entity, ServerConfigBuilder, has been introduced for configuring server properties and replaces the previous configration mechanism of ApplicationPropertiesBuilder. ServerConfigBuilder is used to build instances of the ServerConfig class, which now holds modules, paths, and environment details previously managed by ApplicationProperties.

Additionally, in the embeddedServer() function, the applicationProperties attribute has been renamed to rootConfig to reflect this new configuration approach.

Package

2.x.x

3.0.x

io.ktor:ktor-server-core

ApplicationProperties

ServerConfig

io.ktor:ktor-server-core

ApplciationPropertiesBuilder

ServerConfigBuilder

Introduction of EmbeddedServer

The class EmbeddedServer is introduced and used to replace ApplicationEngine as a return type of the embeddedServer() function.

For more details about the model change, see issue KTOR-3857 on YouTrack.

Testing

withTestApplication and withApplication have been removed

The withTestApplication and withApplication functions, previously deprecated in the 2.0.0 release, have now been removed from the ktor-server-test-host package.

Instead, use the testApplication function with the existing Ktor client instance to make requests to your server and verify the results.

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

For more information, see Testing.

TestApplication module loading

TestApplication no longer automatically loads modules from a configuration file ( e.g. application.conf). Instead, you must explicitly load your modules within the testApplication function or load the configuration file manually.

Explicit module loading

To explicitly load modules, use the application function within testApplication. This approach allows you to manually specify which modules to load, providing greater control over your test setup.

import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.testing.* import kotlin.test.* class ApplicationTest { @Test fun testRoot() = testApplication { client.get("/").apply { assertEquals(HttpStatusCode.OK, status) assertEquals("Hello World!", bodyAsText()) } } }
import com.example.plugins.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.testing.* import kotlin.test.* class ApplicationTest { @Test fun testRoot() = testApplication { application { configureRouting() } client.get("/").apply { assertEquals(HttpStatusCode.OK, status) assertEquals("Hello World!", bodyAsText()) } } }
Load modules from a configuration file

If you want to load modules from a configuration file, use the environment function to specify the configuration file for your test.

@Test fun testHello() = testApplication { environment { config = ApplicationConfig("application-custom.conf") } }

For more information on configuring the test application, see the Testing section.

CallLogging plugin package has been renamed

The CallLogging plugin package has been renamed due to a typo.

2.x.x

3.0.x

io.ktor.server.plugins.callloging

io.ktor.server.plugins.calllogging

ktor-server-host-common module has been removed

Due to Application requiring knowledge of ApplicationEngine, the contents of ktor-server-host-common module have been merged into ktor-server-core, namely the io.ktor.server.engine package.

Ensure that your dependencies are updated accordingly. In most cases, you can simply remove the ktor-server-host-common dependency.

Locations plugin has been removed

The Locations plugin for the Ktor server has been removed. To create type-safe routing, use the Resources plugin instead. This requires the following changes:

  • Replace the io.ktor:ktor-server-locations artifact with io.ktor:ktor-server-resources.

  • The Resources plugin depends on the Kotlin serialization plugin. To add the serialization plugin, see the kotlinx.serialization setup.

  • Update the plugin import from io.ktor.server.locations.* to io.ktor.server.resources.*.

  • Additionally, import the Resource module from io.ktor.resources.

The following example shows how to implement these changes:

import io.ktor.server.locations.* @Location("/articles") class article(val value: Int) fun Application.module() { install(Locations) routing { get<article> { // Get all articles ... call.respondText("List of articles") } } }
import io.ktor.resources.Resource import io.ktor.server.resources.* @Resource("/articles") class Articles(val value: Int) fun Application.module() { install(Resources) routing { get<Articles> { // Get all articles ... call.respondText("List of articles") } } }

For more information on working with Resources, refer to Type-safe routing.

Replacement of java.time in WebSockets configuration

The WebSockets plugin configuration has been updated to use Kotlin’s Duration for the pingPeriod and timeout properties. This replaces the previous use of java.time.Duration for a more idiomatic Kotlin experience.

To migrate existing code to the new format, use the extension functions and properties from the kotlin.time.Duration class to construct durations. In the following example, Duration.ofSeconds() is replaced with Kotlin’s seconds extension:

import java.time.Duration install(WebSockets) { pingPeriod = Duration.ofSeconds(15) timeout = Duration.ofSeconds(15) //.. }
import kotlin.time.Duration.Companion.seconds install(WebSockets) { pingPeriod = 15.seconds timeout = 15.seconds //.. }

You can use similar Kotlin duration extensions (minutes, hours, etc.) as needed for other duration configurations. For more information, see the Duration documentation.

Server socket .bind() is now suspending

To support asynchronous operations in JS and WasmJS environments, the .bind() function for server sockets in both TCPSocketBuilder and UDPSocketBuilder has been updated to a suspending function. This means any calls to .bind() must now be made within a coroutine.

To migrate, ensure .bind() is only called within a coroutine or suspending function. Here's an example of using runBlocking:

runBlocking { val selectorManager = SelectorManager(Dispatchers.IO) val serverSocket = aSocket(selectorManager).tcp().bind("127.0.0.1", 9002) //... }

For more information on working with sockets, see the Sockets documentation.

Session encryption method update

The encryption method offered by the Sessions plugin has been updated to enhance security.

Specifically, the SessionTransportTransformerEncrypt method, which previously derived the MAC from the decrypted session value, now computes it from the encrypted value.

To ensure compatibility with existing sessions, Ktor has introduced the backwardCompatibleRead property. For current configurations, include the property in the constructor of SessionTransportTransformerEncrypt:

install(Sessions) { cookie<UserSession>("user_session") { // ... transform( SessionTransportTransformerEncrypt( secretEncryptKey, // your encrypt key here secretSignKey, // your sign key here backwardCompatibleRead = true ) ) } }

For more information on session encryption in Ktor, see Sign and encrypt session data.

Last modified: 31 October 2024