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:
ApplicationEngineEnvironmentBuilderandapplicationEngineEnvironmentclasses are renamed.start()andstop()methods are removed fromApplicationEngineEnvironment.embeddedServer()returnsEmbeddedServerinstead ofApplicationEngine.
These changes will impact existing code that relies on the previous model.
Renamed classes
Package | 2.x.x | 3.0.x |
|---|---|---|
|
|
|
|
|
|
start() and stop() methods are removed from ApplicationEngineEnvironment
With the merge of ApplicationEngineEnvironment to ApplicationEnvironment, the start() and stop() methods are now only accessible through ApplicationEngine.
2.x.x | 3.0.x |
|---|---|
|
|
|
|
Additionally, in the following table you can see the list of removed properties and their current corresponding ownership:
2.x.x | 3.0.x |
|---|---|
|
|
|
|
|
|
|
|
|
|
The ownership changes can be illustrated through the following example:
commandLineEnvironment() is removed
The commandLineEnvironment() function, used to create an ApplicationEngineEnvironment instance from command line arguments has been removed in Ktor 3.0.0. Instead, you can use the CommandLineConfig function to parse command-line arguments into a configuration object.
To migrate your application from commandLineEnvironment to CommandLineConfig, replace commandLineEnvironment() with a configure block as shown below.
For more information on command-line configuration with embeddedServer, see the Configuration in code topic.
Introduction of ServerConfigBuilder
A new entity, ServerConfigBuilder, has been introduced for configuring server properties and replaces the previous configuration 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.
The following table summarizes the key changes:
Package | 2.x.x | 3.0.x |
|---|---|---|
|
|
|
|
|
|
Additionally, in the embeddedServer() function, the applicationProperties attribute has been renamed to rootConfig to reflect this new configuration approach.
When using embeddedServer(), replace the applicationProperties attribute with rootConfig. Here's an example of using the serverConfig block to configure a server with developmentMode explicitly set to true:
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:
For more information, see Testing in Ktor Server.
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.
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.
For more information on configuring the test application, see the Testing in Ktor Server section.
CallLogging plugin package has been renamed
The CallLogging plugin package has been renamed due to a typo.
2.x.x | 3.0.x |
|---|---|
|
|
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-locationsartifact withio.ktor:ktor-server-resources.The
Resourcesplugin depends on the Kotlin serialization plugin. To add the serialization plugin, see theUpdate the plugin import from
io.ktor.server.locations.*toio.ktor.server.resources.*.Additionally, import the
Resourcemodule fromio.ktor.resources.
The following example shows how to implement these changes:
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:
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:
For more information on working with sockets, see the Sockets documentation.
Multipart form data
New default limit for binary and file items
In Ktor 3.0.0, a default limit of 50MB has been introduced for receiving binary and file items using ApplicationCall.receiveMultipart(). If a received file or binary item exceeds the 50MB limit, an IOException is thrown.
Override the default limit
If your application previously relied on handling files larger than 50MB without explicit configuration, you will need to update your code to avoid unexpected behaviour.
To override the default limit, pass the formFieldLimit parameter when calling .receiveMultipart():
PartData.FileItem.streamProvider() is deprecated
In previous versions of Ktor, the .streamProvider() function in PartData.FileItem was used to access a file item's content as an InputStream. Starting with Ktor 3.0.0, this function has been deprecated.
To migrate your application, replace .streamProvider() with the .provider() function. The .provider() function returns a ByteReadChannel, which is a coroutine-friendly, non-blocking abstraction for reading streams of bytes incrementally. You can then stream data directly from the channel to the file output, using the .copyTo() or .copyAndClose() methods provided by ByteReadChannel.
In the example the .copyAndClose() method transfers data from the ByteReadChannel to a file's WritableByteChannel.
For the full example and more information on working with multipart form data, see Request handling of multipart form data.
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:
For more information on session encryption in Ktor, see Sign and encrypt session data.
Ktor Client
Renaming of HttpResponse's content property
Prior to Ktor 3.0.0, the content property of HttpResponse provided a raw ByteReadChannel to the response content as it is read from the network. Starting with Ktor 3.0.0, the content property has been renamed to rawContent to better reflect its purpose.
SocketTimeoutException is now a typealias
SocketTimeoutException from the io.ktor.client.network.sockets package has been converted from a Kotlin class to an alias for a Java class. This change may cause a NoClassDefFoundError in certain cases and may require updates to existing code.
To migrate your application, ensure your code is not referencing the old class and is compiled with the latest Ktor version. Here's how to update exception checks:
Shared modules
Migration to kotlinx-io
With the 3.0.0 release, Ktor has transitioned to using the kotlinx-io library, which provides a standardized and efficient I/O API across Kotlin libraries. This change improves performance, reduces memory allocations, and simplifies I/O handling. If your project interacts with Ktor's low-level I/O APIs, you may need to update your code to ensure compatibility.
This impacts many classes, such as ByteReadChannel and ByteWriteChannel. Additionally, the following Ktor classes are now backed by kotlinx-io, and their previous implementations are deprecated:
Ktor 2.x | Ktor 3.x |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The deprecated APIs will be supported until Ktor 4.0, but we recommend migrating as soon as possible. To migrate your application, update your code to utilize the corresponding methods from kotlinx-io.
Example: Streaming I/O
If you are handling large file downloads and need an efficient streaming solution, you can replace manual byte array handling with kotlinx-io's optimized streaming APIs.
In Ktor 2.x, handling large file downloads typically involved manually reading available bytes using ByteReadChannel.readRemaining() and writing them to a file using File.appendBytes():
This approach involved multiple memory allocations and redundant data copies.
In Ktor 3.x, ByteReadChannel.readRemaining() now returns a Source, enabling streaming of data using Source.transferTo():
This approach transfers data directly from the channel to the file's sink, minimizing memory allocations and improving performance.
For the full example, see client-download-streaming.
Attribute keys now require exact type matching
In Ktor 3.0.0, AttributeKey instances are now compared by identity and require exact type matching when storing and retrieving values. This ensures type-safety and prevents unintended behaviour caused by mismatched types.
Previously, it was possible to retrieve an attribute with a different generic type than it was stored with, such as using getOrNull<Any>() to fetch an AttributeKey<Boolean>.
To migrate your application, ensure the retrieval type matches the stored type exactly:
Removal of empty artifact
Since Ktor 1.0.0, the empty artifact io.ktor:ktor was published to Maven by mistake. This artifact has been removed starting with Ktor 3.0.0.
If your project includes this artifact as a dependency, you can safely remove it.