What's new in Ktor 3.2.0
Here are the highlights for this feature release:
Ktor Server
Suspendable module functions
Starting with Ktor 3.2.0, application modules have support for suspendable functions.
Previously, adding asynchronous functions inside Ktor modules required the runBlocking
block that could lead to a deadlock on server creation:
You can now use the suspend
keyword, allowing asynchronous code on application startup:
Concurrent module loading
You can also opt into concurrent module loading by adding the ktor.application.startup = concurrent
Gradle property. It launches all application modules independently, so when one suspends, the others are not blocked. This allows for non-sequential loading for dependency injection, and, in some cases, faster loading.
For more information, see Concurrent module loading.
Configuration file deserialization
Ktor 3.2.0 introduces typed configuration loading with a new .property()
extension on the Application
class. You can now deserialize structured configuration sections directly into Kotlin data classes.
This feature simplifies how you access configuration values and significantly reduces boilerplate when working with nested or grouped settings.
Consider the following application.yaml file:
Previously, you had to retrieve each configuration value individually. With the new .property()
extension, you can load the entire configuration section at once:
This feature supports both HOCON and YAML configuration formats and uses kotlinx.serialization
for deserialization.
ApplicationTestBuilder
has a configurable client
Starting with Ktor 3.2.0, the client
property in the ApplicationTestBuilder
class is mutable. Previously, it was read-only. This change lets you configure your own test client and reuse it wherever the ApplicationTestBuilder
class is available. For example, you can access the client from within extension functions:
Dependency injection
Ktor 3.2.0 introduces Dependency Injection (DI) support, making it easier to manage and wire dependencies directly from your configuration files and application code. The new DI plugin simplifies dependency resolution, supports async loading, provides automatic cleanup, and integrates smoothly with testing.
To use DI, include the ktor-server-di
artifact in your build script:
Basic dependency registration
You can register dependencies using lambdas, function references, or constructor references:
Configuration-based dependency registration
You can configure dependencies declaratively using classpath references in your configuration file. This supports both function and class references:
Arguments are resolved automatically through annotations like @Property
and @Named
.
Dependency resolution and injection
Resolving dependencies
To resolve dependencies, you can use property delegation or direct resolution:
Asynchronous dependency resolution
To support asynchronous loading, you can use suspending functions:
The DI plugin will automatically suspend resolve()
calls until all dependencies are ready.
Injecting into application modules
You can inject dependencies directly into application modules by specifying module parameters. Ktor will resolve them from the DI container:
Use @Named
for injecting specifically keyed dependencies:
Property and configuration injection
Use @Property
to inject configuration values directly:
This simplifies working with structured configuration and supports automatic parsing of primitive types.
For more information and advanced usage, see Dependency Injection.
Ktor Client
SaveBodyPlugin
and HttpRequestBuilder.skipSavingBody()
are deprecated
Prior to Ktor 3.2.0, the SaveBodyPlugin
was installed by default. It cached the entire response body in memory, allowing it to be accessed multiple times. To avoid saving response body, the plugin had to be disabled explicitly.
Starting with Ktor 3.2.0, the SaveBodyPlugin
is deprecated and replaced by a new internal plugin that automatically saves the response body for all non-streaming requests. This improves resource management and simplifies the HTTP response lifecycle.
The HttpRequestBuilder.skipSavingBody()
is also deprecated. If you need to handle a response without caching the body, use a streaming approach instead.
This approach streams the response directly, preventing the body from being saved in memory.
The .wrapWithContent()
and .wrap()
extension functions are deprecated
In Ktor 3.2.0, the .wrapWithContent()
and .wrap()
extension functions are deprecated in favor of the new .replaceResponse()
function.
The .wrapWithContent()
and .wrap()
functions replace the original response body with a ByteReadChannel
that can only be read once. If the same channel instance is passed directly instead of a function that returns a new one, reading the body multiple times fails. This can break compatibility between different plugins accessing the response body, because the first plugin to read it consumes the body:
To avoid this issue, use the .replaceResponse()
function instead. It accepts a lambda that returns a new channel on each access, ensuring safe integration with other plugins:
Access resolved IP address
You can now use the new .resolveAddress()
function on io.ktor.network.sockets.InetSocketAddress
instances. This function allows you to obtain the raw resolved IP address of the associated host:
It returns the resolved IP address as a ByteArray
, or null
if the address cannot be resolved. The size of the returned ByteArray
depends on the IP version: it will contain 4 bytes for IPv4 addresses and 16 bytes for IPv6 addresses. On JS and Wasm platforms, .resolveAddress()
will always return null
.
Shared
HTMX Integration
Ktor 3.2.0 introduces experimental support for HTMX, a modern JavaScript library that enables dynamic interactions via HTML attributes like hx-get
and hx-swap
. Ktor’s HTMX integration provides:
HTMX-aware routing for handling HTMX requests based on headers.
HTML DSL extensions to generate HTMX attributes in Kotlin.
HTMX header constants and values to eliminate string literals.
Ktor’s HTMX support is available across three experimental modules:
Module | Description |
---|---|
| Core definitions and header constants |
| Integration with the Kotlin HTML DSL |
| Routing support for HTMX-specific requests |
All APIs are marked with @ExperimentalKtorApi
and require opt-in via @OptIn(ExperimentalKtorApi::class)
. For more information, see HTMX integration.
Unix domain sockets
With 3.2.0, you can set up Ktor clients to connect to Unix domain sockets and Ktor servers to listen to such sockets. Currently, Unix domain sockets are only supported in the CIO engine.
Example of a server configuration:
Connecting to that socket using a Ktor client:
You can also use a Unix domain socket in a default request.
Infrastructure
Published version catalog
With this release, you can now use an official published version catalog to manage all Ktor dependencies from a single source. This eliminates the need to manually declare Ktor versions in your dependencies.
To add the catalog to your project, configure Gradle’s version catalog in settings.gradle.kts, then reference it in your module’s build.gradle.kts file:
Gradle plugin
Enabling development mode
Ktor 3.2.0 simplifies enabling development mode. Previously, enabling development mode required explicit configuration in the application
block. Now, you can use the ktor.development
property to enable it, either dynamically or explicitly:
Dynamically enable development mode based on a project property.
ktor { development = project.ext.has("development") }Explicitly set development mode to true.
ktor { development = true }
By default, the ktor.development
value is automatically resolved from the Gradle project property or the system property io.ktor.development
if either is defined. This allows you to enable development mode directly using a Gradle CLI flag: