| 3.3.3 | November 26, 2025 |
A patch release that adds HTTP/2 over cleartext (h2c) support on Jetty Client, improves logging and OpenAPI generation,
diff --git a/topics/server-api-key-auth.md b/topics/server-api-key-auth.md
new file mode 100644
index 000000000..edaed06c2
--- /dev/null
+++ b/topics/server-api-key-auth.md
@@ -0,0 +1,285 @@
+[//]: # (title: API Key authentication)
+
+
+
+
+
+
+
+Required dependencies: io.ktor:ktor-server-auth, io.ktor:%artifact_name%
+
+
+
+
+API Key authentication is a simple authentication method where clients pass a secret key as part of their request,
+typically in a header. This key serves as both the identifier and the authentication mechanism.
+
+Ktor allows you to use API Key authentication for securing [routes](server-routing.md) and validating client requests.
+
+> You can get general information about authentication in Ktor in the [](server-auth.md) section.
+
+> API Keys should be kept secret and transmitted securely. It's recommended to use [HTTPS/TLS](server-ssl.md) to protect
+> API keys in transit.
+>
+{style="note"}
+
+## Add dependencies {id="add_dependencies"}
+
+To enable API Key authentication, add the `ktor-server-auth` and `%artifact_name%` artifacts in the build script:
+
+
+
+
+```kotlin
+implementation("io.ktor:%artifact_name%:$ktor_version")
+implementation("io.ktor:ktor-server-auth:$ktor_version")
+```
+
+
+
+```Groovy
+implementation "io.ktor:%artifact_name%:$ktor_version"
+implementation "io.ktor:ktor-server-auth:$ktor_version"
+
+```
+
+
+
+
+```xml
+
+ io.ktor
+ %artifact_name%-jvm
+ ${ktor_version}
+
+
+ io.ktor
+ ktor-server-auth
+ ${ktor_version}
+
+```
+
+
+
+
+## API Key authentication flow {id="flow"}
+
+The API Key authentication flow looks as follows:
+
+1. A client makes a request with an API key included in a header (typically `X-API-Key`) to a
+ specific [route](server-routing.md) in a server application.
+2. The server validates the API key using custom validation logic.
+3. If the key is valid, the server responds with the requested content. If the key is invalid or missing, the server
+ responds with a `401 Unauthorized` status.
+
+## Install API Key authentication {id="install"}
+
+To install the `apiKey` authentication provider, call
+the [`apiKey`](https://api.ktor.io/ktor-server-auth/io.ktor.server.auth/api-key.html) function inside the
+`install(Authentication)` block:
+
+```kotlin
+import io.ktor.server.application.*
+import io.ktor.server.auth.*
+// ...
+install(Authentication) {
+ apiKey {
+ // Configure API Key authentication
+ }
+}
+```
+
+You can optionally specify a [provider name](server-auth.md#provider-name) that can be used
+to [authenticate a specified route](#authenticate-route).
+
+## Configure API Key authentication {id="configure"}
+
+In this section, we'll see the configuration specifics of the `apiKey` authentication provider.
+
+> To learn how to configure different authentication providers in Ktor, see [](server-auth.md#configure).
+
+### Step 1: Configure an API Key provider {id="configure-provider"}
+
+The `apiKey` authentication provider exposes its settings via
+the [`ApiKeyAuthenticationProvider.Config`](https://api.ktor.io/ktor-server-auth/io.ktor.server.auth/-api-key-authentication-provider/-config/index.html)
+class. In the example below, the following settings are specified:
+
+* The `validate` function receives the API key extracted from the request and returns a `Principal` in the case of
+ successful authentication or `null` if authentication fails.
+
+Here's a minimal example:
+
+```kotlin
+data class AppPrincipal(val key: String) : Principal
+
+install(Authentication) {
+ apiKey {
+ validate { keyFromHeader ->
+ val expectedApiKey = "this-is-expected-key"
+ keyFromHeader
+ .takeIf { it == expectedApiKey }
+ ?.let { AppPrincipal(it) }
+ }
+ }
+}
+```
+
+#### Customize key location {id="key-location"}
+
+By default, the `apiKey` provider looks for the API key in the `X-API-Key` header.
+
+You can use `headerName` to specify a custom header:
+
+```kotlin
+apiKey("api-key-header") {
+ headerName = "X-Secret-Key"
+ validate { key ->
+ // ...
+ }
+}
+```
+
+### Step 2: Validate API keys {id="validate"}
+
+The validation logic depends on your application's requirements. Here are common approaches:
+
+#### Static key comparison {id="static-key"}
+
+For simple cases, you can compare against a predefined key:
+
+```kotlin
+apiKey {
+ validate { keyFromHeader ->
+ val expectedApiKey = environment.config.property("api.key").getString()
+ keyFromHeader
+ .takeIf { it == expectedApiKey }
+ ?.let { AppPrincipal(it) }
+ }
+}
+```
+
+> Store sensitive API keys in configuration files or environment variables, not in source code.
+>
+{style="note"}
+
+#### Database lookup {id="database-lookup"}
+
+For multiple API keys, validate against a database:
+
+```kotlin
+apiKey {
+ validate { keyFromHeader ->
+ // Looks up the key in the database
+ val user = database.findUserByApiKey(keyFromHeader)
+ user?.let { UserIdPrincipal(it.username) }
+ }
+}
+```
+
+#### Multiple validation criteria {id="multiple-criteria"}
+
+You can implement complex validation logic:
+
+```kotlin
+apiKey {
+ validate { keyFromHeader ->
+ val apiKey = database.findApiKey(keyFromHeader)
+
+ // Checks if the key exists, is active, and not expired
+ if (apiKey != null &&
+ apiKey.isActive &&
+ apiKey.expiresAt > Clock.System.now()
+ ) {
+ UserIdPrincipal(apiKey.userId)
+ } else {
+ null
+ }
+ }
+}
+```
+
+### Step 3: Configure challenge {id="challenge"}
+
+You can customize the response sent when authentication fails using the `challenge` function:
+
+```kotlin
+apiKey {
+ validate { key ->
+ // Validation logic
+ }
+ challenge { defaultScheme, realm ->
+ call.respond(
+ HttpStatusCode.Unauthorized,
+ "Invalid or missing API key"
+ )
+ }
+}
+```
+
+### Step 4: Protect specific resources {id="authenticate-route"}
+
+After configuring the `apiKey` provider, you can protect specific resources in your application using the
+[`authenticate`](server-auth.md#authenticate-route) function. In the case of successful authentication, you can
+retrieve an authenticated principal inside a route handler using the `call.principal` function.
+
+```kotlin
+routing {
+ authenticate {
+ get("/") {
+ val principal = call.principal()!!
+ call.respondText("Hello, authenticated client! Your key: ${principal.key}")
+ }
+ }
+}
+```
+
+## API Key authentication example {id="complete-example"}
+
+Here's a complete minimal example of API Key authentication:
+
+```kotlin
+import io.ktor.server.application.*
+import io.ktor.server.auth.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+
+data class AppPrincipal(val key: String) : Principal
+
+fun Application.module() {
+ val expectedApiKey = "this-is-expected-key"
+
+ install(Authentication) {
+ apiKey {
+ validate { keyFromHeader ->
+ keyFromHeader
+ .takeIf { it == expectedApiKey }
+ ?.let { AppPrincipal(it) }
+ }
+ }
+ }
+
+ routing {
+ authenticate {
+ get("/") {
+ val principal = call.principal()!!
+ call.respondText("Key: ${principal.key}")
+ }
+ }
+ }
+}
+```
+
+## Best practices {id="best-practices"}
+
+When implementing API Key authentication, consider the following best practices:
+
+1. **Use HTTPS**: Always transmit API keys over HTTPS to prevent interception.
+2. **Store securely**: Never hardcode API keys in source code. Use environment variables or secure configuration
+ management.
+3. **Key rotation**: Implement a mechanism for rotating API keys periodically.
+4. **Rate limiting**: Combine API Key authentication with rate limiting to prevent abuse.
+5. **Logging**: Log authentication failures for security monitoring, but never log the actual API keys.
+6. **Key format**: Use cryptographically secure random strings for API keys (for example, UUID or base64-encoded random
+ bytes).
+7. **Multiple keys**: Consider supporting multiple API keys per user for different applications or purposes.
+8. **Expiration**: Implement key expiration for enhanced security.
diff --git a/topics/server-auth.md b/topics/server-auth.md
index 3e01a9d4e..44723d3f3 100644
--- a/topics/server-auth.md
+++ b/topics/server-auth.md
@@ -33,8 +33,9 @@ Ktor supports the following authentication and authorization schemes:
HTTP provides a [general framework](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) for access control and authentication. In Ktor, you can use the following HTTP authentication schemes:
* [Basic](server-basic-auth.md) - uses `Base64` encoding to provide a username and password. Generally is not recommended if not used in combination with HTTPS.
* [Digest](server-digest-auth.md) - an authentication method that communicates user credentials in an encrypted form by applying a hash function to the username and password.
-* [Bearer](server-bearer-auth.md) - an authentication scheme that involves security tokens called bearer tokens.
+* [Bearer](server-bearer-auth.md) - an authentication scheme that involves security tokens called bearer tokens.
The Bearer authentication scheme is used as part of [OAuth](server-oauth.md) or [JWT](server-jwt.md), but you can also provide custom logic for authorizing bearer tokens.
+* [API Key](server-api-key-auth.md) - a simple authentication method where clients pass a secret key in a header.
### Form-based authentication {id="form-auth"}
diff --git a/topics/server-compression.md b/topics/server-compression.md
index 519023f0e..e4fc372e0 100644
--- a/topics/server-compression.md
+++ b/topics/server-compression.md
@@ -16,8 +16,13 @@
-Ktor provides the capability to compress response body and decompress request body by using the [Compression](https://api.ktor.io/ktor-server-compression/io.ktor.server.plugins.compression/-compression.html) plugin.
-You can use different compression algorithms, including `gzip` and `deflate`, specify the required conditions for compressing data (such as a content type or response size), or even compress data based on specific request parameters.
+Ktor provides the capability to compress response body and decompress request body by using the [`Compression`](https://api.ktor.io/ktor-server-compression/io.ktor.server.plugins.compression/-compression.html)
+plugin.
+
+With the `Compression` plugin, you can:
+- Use different compression algorithms, including `gzip`, `ztsd` and `deflate`.
+- Specify the required conditions for compressing data, such as a content type or response size.
+- Compress data based on specific request parameters.
> Note that the `%plugin_name%` plugin does not currently support `SSE` responses.
>
@@ -30,6 +35,11 @@ You can use different compression algorithms, including `gzip` and `deflate`, sp
+To include Zstandard compression, add the `ktor-server-compression-zstd` dependency:
+
+
+
+
## Install %plugin_name% {id="install_plugin"}
@@ -51,6 +61,7 @@ To enable only specific encoders, call the corresponding extension functions, fo
install(Compression) {
gzip()
deflate()
+ ztsd()
}
```
@@ -64,10 +75,13 @@ install(Compression) {
deflate {
priority = 1.0
}
+ ztsd {
+ priority = 0.8
+ }
}
```
-In the example above, `deflate` has a higher priority value and takes precedence over `gzip`. Note that the server first
+In the example above, `deflate` has a higher priority value and takes precedence over `gzip` and `ztsd`. Note that the server first
looks at the [quality](https://developer.mozilla.org/en-US/docs/Glossary/Quality_Values) values within
the [Accept-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) header and then takes
into account the specified priorities.
@@ -94,10 +108,10 @@ value. To do this, pass the desired value (in bytes) to the `minimumSize` functi
```kotlin
install(Compression) {
- deflate {
- minimumSize(1024)
- }
+ deflate {
+ minimumSize(1024)
}
+}
```
@@ -132,6 +146,17 @@ install(Compression) {
}
```
+## Zstandard compression Level {id="compression_level"}
+
+You can configure the compression level for `zstd` using the `level` parameter. The default compression level is `3`, but you can adjust it based on your needs.
+
+```kotlin
+install(Compression) {
+ // Defaults to level = 3
+ zstd(level = 20)
+}
+```
+
## Implement custom encoder {id="custom_encoder"}
If necessary, you can provide your own encoder by implementing
diff --git a/topics/server-html-dsl.md b/topics/server-html-dsl.md
index 5bf6ec54f..4c12a90e6 100644
--- a/topics/server-html-dsl.md
+++ b/topics/server-html-dsl.md
@@ -28,7 +28,7 @@ The example below shows a sample HTML DSL and a corresponding HTML to be sent to
```kotlin
```
-{src="snippets/html/src/main/kotlin/com/example/Application.kt" include-lines="3-8,11-29"}
+{src="snippets/html/src/main/kotlin/com/example/Application.kt" include-lines="3-8,11-27,35-36"}
@@ -47,7 +47,7 @@ The example below shows a sample HTML DSL and a corresponding HTML to be sent to
-The following [example](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/auth-form-html-dsl) shows how to respond with an HTML form used to collect [credential information](server-form-based-auth.md) from a user:
+The following example shows how to respond with an HTML form used to collect [credential information](server-form-based-auth.md) from a user:
@@ -74,10 +74,44 @@ The following [example](https://github.com/ktorio/ktor-documentation/tree/%ktor_
-You can learn how to receive form parameters on the server side from [](server-requests.md#form_parameters).
+For the full example, see [auth-form-html-dsl](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/auth-form-html-dsl).
+> To learn more about receiving form parameters on the server side, see [](server-requests.md#form_parameters).
+>
> To learn more about generating HTML using kotlinx.html, see the [kotlinx.html wiki](https://github.com/Kotlin/kotlinx.html/wiki).
+## Send partial HTML {id="html_fragments"}
+
+In addition to generating full HTML documents, you can also respond with HTML fragments using the `.respondHtmlFragment()`
+function.
+
+HTML fragments are useful when returning partial markup that does not require a full `` document, such as dynamic
+updates used by libraries like HTMX.
+
+
+
+
+```kotlin
+```
+{src="snippets/html/src/main/kotlin/com/example/Application.kt" include-lines="3-8,11-12,28-36"}
+
+
+
+
+```html
+
+
+ Created!
+
+
+
+```
+
+
+
+
+This function works similarly to `.respondHtml()`, but it renders only the content you define inside the builder,
+without adding root HTML elements.
## Templates {id="templates"}
diff --git a/topics/server-http-request-lifecycle.md b/topics/server-http-request-lifecycle.md
new file mode 100644
index 000000000..ee6b3b720
--- /dev/null
+++ b/topics/server-http-request-lifecycle.md
@@ -0,0 +1,65 @@
+[//]: # (title: HTTP request lifecycle)
+
+
+
+
+
+
+
+
+Required dependencies: io.ktor:ktor-server-core
+
+
+
+Supported engines: Netty, CIO
+
+
+
+
+By default, Ktor continues processing a request even if the client disconnects. The
+[`%plugin_name%`](https://api.ktor.io/ktor-http/io.ktor.http/-http-request-lifecycle.html)
+plugin allows you to cancel request processing as soon as the client disconnects.
+
+This is useful for long-running or resource-intensive requests that should stop executing when the client is no longer
+waiting for a response.
+
+## Install and configure %plugin_name% {id="install_plugin"}
+
+To enable the `HttpRequestLifecycle` plugin, install it in your application module using the `install` function and
+set the `cancelCallOnClose` property:
+
+```kotlin
+```
+{src="snippets/server-http-request-lifecycle/src/main/kotlin/com/example/Application.kt" include-lines="9-10,18-22,37"}
+
+When the `cancelCallOnClose` property is enabled, the `%plugin_name%` plugin installs a cancellation handler per
+request. When a client disconnects, only the coroutine handling that specific route is canceled.
+
+Cancellation propagates through structured concurrency, so any child coroutines started from the request coroutine
+(for example, using `launch` or `async`) are also canceled. A `CancellationException` is thrown at the next
+suspension point.
+
+## Handling cancellation {id="handle_cancellation"}
+
+You can catch `CancellationException` to perform cleanup operations, such as releasing resources or stopping background
+work:
+
+```kotlin
+```
+{src="snippets/server-http-request-lifecycle/src/main/kotlin/com/example/Application.kt" include-lines="19-37"}
+
+> Coroutine cancellation is cooperative. Blocking or CPU-bound code is not interrupted automatically.
+> If request handling performs long-running work, call
+> `call.coroutineContext.ensureActive()` to react to cancellation.
+>
+> To learn more, see
+> [Coroutine cancellation](https://kotlinlang.org/docs/cancellation-and-timeouts.html).
+{style="note"}
+
+> For the full example, see [%example_name%](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/%example_name%).
+
+## Limitations
+
+This plugin is only fully supported on `CIO` and `Netty` engines. Engines based on servlets (or other unsupported engines)
+cannot detect client disconnects reliably. Cancellation can only be detected when the server attempts to write a
+response.
\ No newline at end of file
diff --git a/topics/server-oauth.md b/topics/server-oauth.md
index 460d48cf4..d53190ed3 100644
--- a/topics/server-oauth.md
+++ b/topics/server-oauth.md
@@ -38,7 +38,7 @@ request the resource.
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="14,28-32,104,131-132"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="14,29-33,115,141-143"}
## OAuth authorization flow {id="flow"}
@@ -77,7 +77,7 @@ For example, to install an `oauth` provider with the name "auth-oauth-google" it
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="9-10,28-29,34-37,57-58,104"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="9-10,29-30,35-38,65-66,115"}
## Configure OAuth {id="configure-oauth"}
@@ -111,7 +111,7 @@ JSON serializer is required to deserialize received JSON data [after a request t
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="21-27"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="22-28"}
The client instance is passed to the `main` [module function](server-modules.md) to have the capability to create a separate
client instance in a server [test](server-testing.md).
@@ -119,7 +119,7 @@ client instance in a server [test](server-testing.md).
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="29,104"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="30,115"}
### Step 2: Configure the OAuth provider {id="configure-oauth-provider"}
@@ -128,13 +128,14 @@ The code snippet below shows how to create and configure the `oauth` provider wi
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="33-58"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="34-66"}
* The `urlProvider` specifies a [redirect route](#redirect-route) that will be invoked when authorization is completed.
> Make sure that this route is added to a list of [**Authorised redirect URIs**](#authorization-credentials).
* `providerLookup` allows you to specify OAuth settings for a required provider. These settings are represented by
the [OAuthServerSettings](https://api.ktor.io/ktor-server-auth/io.ktor.server.auth/-o-auth-server-settings/index.html)
class and allow Ktor to make automatic requests to the OAuth server.
+* The `fallback` property handles OAuth flow errors by responding with a redirect or custom response.
* The `client` property specifies the [HttpClient](#create-http-client) used by Ktor to make requests to the OAuth
server.
@@ -149,7 +150,7 @@ in [providerLookup](#configure-oauth-provider).
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="59-63,79,103"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="67-71,87,114"}
A user will see the authorization page with the level of permissions required for a Ktor application. These permissions
depend on `defaultScopes` specified in [providerLookup](#configure-oauth-provider).
@@ -167,7 +168,7 @@ returned by the OAuth server.
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="59-79,103"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="67-87,114"}
In this example, the following actions are performed after receiving a token:
@@ -186,14 +187,14 @@ Create a new function called `getPersonalGreeting` which will make the request a
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="106-113"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="117-124"}
Then, you can call the function within a `get` route to retrieve a user's information:
```kotlin
```
-{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="96-102"}
+{src="snippets/auth-oauth-google/src/main/kotlin/com/example/oauth/google/Application.kt" include-lines="104-110"}
For the complete runnable example,
see [auth-oauth-google](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/auth-oauth-google).
diff --git a/topics/server-openapi.md b/topics/server-openapi.md
index 77a2be060..0df37fa10 100644
--- a/topics/server-openapi.md
+++ b/topics/server-openapi.md
@@ -19,9 +19,14 @@
The OpenAPI plugin allows you to generate OpenAPI documentation for your project.
-Ktor allows you to generate and serve OpenAPI documentation for your project based on an existing OpenAPI specification.
-You can serve an existing YAML or JSON specification, or generate one using the
-[OpenAPI extension](openapi-spec-generation.md) of the Ktor Gradle plugin.
+Ktor allows you to serve OpenAPI documentation based on an OpenAPI specification.
+
+You can provide the OpenAPI specification in one of the following ways:
+
+* [Serve an existing YAML or JSON file](#static-openapi-file).
+* [Generate the specification at runtime using the OpenAPI compiler extension and runtime APIs](#generate-runtime-openapi-metadata).
+
+In both cases, the OpenAPI plugin assembles the specification on the server and renders the documentation as HTML.
## Add dependencies {id="add_dependencies"}
@@ -39,11 +44,19 @@ You can serve an existing YAML or JSON specification, or generate one using the
You can replace `$swagger_codegen_version` with the required version of the `swagger-codegen-generators` artifact, for example, `%swagger_codegen_version%`.
+> In Ktor 3.4.0, the `OpenAPI` plugin requires the `ktor-server-routing-openapi` dependency.
+> This was not an intentional breaking change and will be corrected in Ktor 3.4.1.
+> Add the dependency manually if you are using Ktor 3.4.0 to avoid runtime errors.
+>
+{style="warning"}
+
+## Use a static OpenAPI file {id="static-openapi-file"}
-## Configure OpenAPI {id="configure-swagger"}
+To serve OpenAPI documentation from an existing specification, use the [`openAPI()`](%plugin_api_link%) function with
+a provided path to the OpenAPI document.
-To serve OpenAPI documentation, you need to call the [openAPI](%plugin_api_link%) method that creates a `GET` endpoint with documentation
-at the `path` rendered from the OpenAPI specification placed at `swaggerFile`:
+The following example creates a `GET` endpoint at the `openapi` path and renders the Swagger UI from the provided
+OpenAPI specification file:
```kotlin
import io.ktor.server.plugins.openapi.*
@@ -54,14 +67,37 @@ routing {
}
```
-This method tries to look up the OpenAPI specification in the application resources.
-Otherwise, it tries to read the OpenAPI specification from the file system using `java.io.File`.
+The plugin first looks for the specification in the application resources. If not found, it attempts to load it from
+the file system using `java.io.File`.
-By default, the documentation is generated using `StaticHtml2Codegen`.
-You can customize generation settings inside the `openAPI` block:
+## Generate runtime OpenAPI metadata
+
+Instead of relying on a static file, you can generate the OpenAPI specification at runtime using metadata produced
+by the OpenAPI compiler plugin and route annotations.
+
+In this mode, the OpenAPI plugin assembles the specification directly from the routing tree:
```kotlin
+ openAPI(path = "openapi") {
+ info = OpenApiInfo("My API", "1.0")
+ source = OpenApiDocSource.Routing {
+ routingRoot.descendants()
+ }
+}
```
-{src="snippets/json-kotlinx-openapi/src/main/kotlin/com/example/Application.kt" include-lines="40,56-58,59"}
-You can now [run](server-run.md) the application and open the `/openapi` page to see the generated documentation.
+With this, you can access the generated OpenAPI documentation at the `/openapi` path, reflecting the current state of the
+application.
+
+> For more information on the OpenAPI compiler extension and runtime APIs, see [](openapi-spec-generation.md).
+>
+{style="tip"}
+
+## Configure OpenAPI {id="configure-openapi"}
+
+By default, documentation is rendered using `StaticHtml2Codegen`. You can customize the renderer inside the `openAPI {}`
+block:
+
+```kotlin
+```
+{src="snippets/json-kotlinx-openapi/src/main/kotlin/com/example/Application.kt" include-lines="40,56-58,59"}
diff --git a/topics/server-responses.md b/topics/server-responses.md
index df4cb2ad9..ca33f0d16 100644
--- a/topics/server-responses.md
+++ b/topics/server-responses.md
@@ -4,18 +4,22 @@
Learn how to send different types of responses.
-Ktor allows you to handle incoming [requests](server-requests.md) and send responses inside [route handlers](server-routing.md#define_route). You can send different types of responses: plain text, HTML documents and templates, serialized data objects, and so on. For each response, you can also configure various [response parameters](#parameters), such as a content type, headers, and cookies.
+Ktor allows you to handle incoming [requests](server-requests.md) and send responses inside [route handlers](server-routing.md#define_route). You can send
+different types of responses: plain text, HTML documents and templates, serialized data objects, and so on. You can also
+configure various [response parameters](#parameters), such as content type, headers, cookies, and the status code.
Inside a route handler, the following API is available for working with responses:
-* A set of functions targeted for [sending a specific content type](#payload), for example, [call.respondText](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-text.html), [call.respondHtml](https://api.ktor.io/ktor-server-html-builder/io.ktor.server.html/respond-html.html), and so on.
-* The [call.respond](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond.html) function that allows you to [send any data](#payload) inside a response. For example, with the enabled [ContentNegotiation](server-serialization.md) plugin, you can send a data object serialized in a specific format.
-* The [call.response](https://api.ktor.io/ktor-server-core/io.ktor.server.application/-application-call/response.html) property that returns the [ApplicationResponse](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/index.html) object that provides access to [response parameters](#parameters) and allows you to set the status code, add headers, and configure cookies.
-* The [call.respondRedirect](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-redirect.html) provides the capability to add redirects.
+* A set of functions for [sending specific content types](#payload), such as [`call.respondText()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-text.html) and [`call.respondHtml()`](https://api.ktor.io/ktor-server-html-builder/io.ktor.server.html/respond-html.html).
+* The [`call.respond()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond.html) function that allows you to [send any data type](#payload) inside a response. When the [ContentNegotiation](server-serialization.md) plugin is installed, you can send a data object serialized in a specific format.
+* The [`call.response()`](https://api.ktor.io/ktor-server-core/io.ktor.server.application/-application-call/response.html) property that returns the [`ApplicationResponse`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/index.html) object, providing access to [response parameters](#parameters) for setting the status code, adding headers, and configuring cookies.
+* The [`call.respondRedirect()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-redirect.html) function for sending redirect responses.
## Set response payload {id="payload"}
+
### Plain text {id="plain-text"}
-To send a plain text in a response, use the [call.respondText](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-text.html) function:
+
+To send plain text, use the [`call.respondText()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-text.html) function:
```kotlin
get("/") {
call.respondText("Hello, world!")
@@ -23,21 +27,36 @@ get("/") {
```
### HTML {id="html"}
-Ktor provides two main ways to send HTML responses to a client:
-* By building HTML using Kotlin HTML DSL.
-* By using JVM template engines, such as FreeMarker, Velocity, and so on.
-To send HTML build using Kotlin DSL, use the [call.respondHtml](https://api.ktor.io/ktor-server-html-builder/io.ktor.server.html/respond-html.html) function:
+Ktor provides two main mechanisms for generating HTML responses:
+* Building HTML using the Kotlin HTML DSL.
+* Rendering templates using JVM template engines such as [FreeMarker](https://freemarker.apache.org/) or [Velocity](https://velocity.apache.org/engine/).
+
+#### Full HTML documents
+
+To send full HTML documents built with Kotlin DSL, use the [`call.respondHtml()`](https://api.ktor.io/ktor-server-html-builder/io.ktor.server.html/respond-html.html) function:
+
+```kotlin
+```
+{src="snippets/html/src/main/kotlin/com/example/Application.kt" include-lines="13-27"}
+
+#### Partial HTML fragments
+
+If you need to return only a fragment of HTML, without wrapping it in ``, ``, or ``, you can use
+`call.respondHtmlFragment()`:
+
```kotlin
```
-{src="snippets/html/src/main/kotlin/com/example/Application.kt" include-lines="12-28"}
+{src="snippets/html/src/main/kotlin/com/example/Application.kt" include-lines="28-35"}
-To send a template in a response, call the [call.respond](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond.html) function with a specific content ...
+#### Templates
+
+To send a template in a response, use the [`call.respond()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond.html) function with a specific content:
```kotlin
```
{src="snippets/freemarker/src/main/kotlin/com/example/Application.kt" include-lines="16-19"}
-... or use an appropriate [call.respondTemplate](https://api.ktor.io/ktor-server-freemarker/io.ktor.server.freemarker/respond-template.html) function:
+You can also use the [`call.respondTemplate()`](https://api.ktor.io/ktor-server-freemarker/io.ktor.server.freemarker/respond-template.html) function:
```kotlin
get("/index") {
val sampleUser = User(1, "John")
@@ -48,64 +67,122 @@ You can learn more from the [](server-templating.md) help section.
### Object {id="object"}
-To enable serialization of data objects in Ktor, you need to install the [ContentNegotiation](server-serialization.md) plugin and register a required converter (for example, JSON). Then, you can use the [call.respond](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond.html) function to pass a data object in a response:
+
+To enable serialization of data objects in Ktor, you need to install the [ContentNegotiation](server-serialization.md)
+plugin and register a required converter (for example, JSON). Then, you can use the [`call.respond()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond.html)
+function to pass a data object in a response:
```kotlin
```
{src="snippets/json-kotlinx/src/main/kotlin/jsonkotlinx/Application.kt" include-lines="32-36"}
-You can find the full example here: [json-kotlinx](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/json-kotlinx).
+For the full example, see [json-kotlinx](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/json-kotlinx).
[//]: # (TODO: Check link for LocalPathFile)
### File {id="file"}
-To respond to a client with a content of a file, you have two options:
+To respond to a client with the content of a file, you have two options:
-- For `File` resources, use
- the [call.respondFile](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-file.html)
+- For a file represented as a `File` object, use
+ the [`call.respondFile()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-file.html)
function.
-- For `Path` resources, use the `call.respond()` function with
- the [LocalPathContent](https://api.ktor.io/ktor-server-core/io.ktor.server.http.content/-local-path-content/index.html)
+- For a file pointed by the given `Path` object, use the `call.respond()` function with
+ the [`LocalPathContent`](https://api.ktor.io/ktor-server-core/io.ktor.server.http.content/-local-path-content/index.html)
class.
-A code sample below shows how to send a specified file in a response and make this file downloadable by adding the `Content-Disposition` [header](#headers):
+The example below shows how to send a file and make it downloadable by adding the `Content-Disposition` [header](#headers):
```kotlin
```
{src="snippets/download-file/src/main/kotlin/com/example/DownloadFile.kt" include-lines="3-35"}
-Note that this sample has two plugins installed:
-- [PartialContent](server-partial-content.md) enables the server to respond to requests with the `Range` header and send only a portion of content.
-- [AutoHeadResponse](server-autoheadresponse.md) provides the ability to automatically respond to `HEAD` request for every route that has a `GET` defined. This allows the client application to determine the file size by reading the `Content-Length` header value.
+Note that this sample uses two plugins:
+- [`PartialContent`](server-partial-content.md) enables the server to respond to requests with the `Range` header and send only a portion of content.
+- [`AutoHeadResponse`](server-autoheadresponse.md) provides the ability to automatically respond to a ` HEAD ` request for every route that has a `GET` defined. This allows the client application to determine the file size by reading the `Content-Length` header value.
For the full code sample,
see [download-file](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/download-file).
+### Resource
+
+You can serve a single resource from the classpath with the [`call.respondResource()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-resource.html) method.
+This method accepts the path to the resource and sends a response constructed in the following way:
+it reads the response body from the resource stream, and derives the `Content-Type` header from the file extension.
+
+The following example shows the method call in a route handler:
+
+```kotlin
+routing {
+ get("/resource") {
+ call.respondResource("public/index.html")
+ }
+}
+```
+
+In the example above, since the resource extension is `.html`, the response will include the `Content-Type: text/html` header.
+For convenience, you can pass components of the resource location, namely relative path and package, separately through the first and second parameters.
+The following example resolves resources under the `assets` package based on the requested path:
+
+```kotlin
+get("/assets/{rest-path...}") {
+ var path = call.parameters["rest-path"]
+ if (path.isNullOrEmpty()) {
+ path = "index.html"
+ }
+
+ try {
+ call.respondResource(path, "assets") {
+ application.log.info(this.contentType.toString())
+ }
+ } catch (_: IllegalArgumentException) {
+ call.respond(HttpStatusCode.NotFound)
+ }
+}
+```
+
+If the requested path after the `/assets` prefix is empty or `/`, the handler uses the default `index.html` resource to
+respond. If no resource is found at the given path, `IllegalArgumentException` is thrown.
+The previous code snippet mimics a more general solution — serving resources from a package with the [`staticResources()`](server-static-content.md#resources) method.
+
### Raw payload {id="raw"}
-If you need to send the raw body payload, use the [call.respondBytes](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-bytes.html) function.
+
+To send the raw body payload, use the [`call.respondBytes()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-bytes.html) function.
## Set response parameters {id="parameters"}
+
### Status code {id="status"}
-To set a status code for a response, call [ApplicationResponse.status](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/status.html). You can pass a predefined status code value ...
+
+To set a status code for a response, call the [`ApplicationResponse.status()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/status.html)
+function with a predefined status code value:
+
```kotlin
get("/") {
call.response.status(HttpStatusCode.OK)
}
```
-... or specify a custom status code:
+
+You can also specify custom status values:
+
```kotlin
get("/") {
call.response.status(HttpStatusCode(418, "I'm a tea pot"))
}
```
-Note that functions for sending a [payload](#payload) have overloads for specifying a status code.
+> All payload-sending functions also provide overloads that accept a status code.
+>
+{style="note"}
### Content type {id="content-type"}
-With the installed [ContentNegotiation](server-serialization.md) plugin, Ktor chooses a content type for a [response](#payload) automatically. If required, you can specify a content type manually by passing a corresponding parameter. For example, the `call.respondText` function in a code snippet below accepts `ContentType.Text.Plain` as a parameter:
+
+With the [ContentNegotiation](server-serialization.md) plugin installed, Ktor chooses a content type automatically. If required, you can
+specify a content type manually by passing a corresponding parameter.
+
+In the example below, the `call.respondText()` function accepts `ContentType.Text.Plain` as a parameter:
+
```kotlin
get("/") {
call.respondText("Hello, world!", ContentType.Text.Plain, HttpStatusCode.OK)
@@ -113,51 +190,56 @@ get("/") {
```
### Headers {id="headers"}
-There are several ways to send specific headers in a response:
-* Add a header to the [ApplicationResponse.headers](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/headers.html) collection:
+
+You can add headers to a response in several ways:
+* Modify the [`ApplicationResponse.headers`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/headers.html) collection:
```kotlin
get("/") {
call.response.headers.append(HttpHeaders.ETag, "7c876b7e")
}
```
-* Call the [ApplicationResponse.header](https://api.ktor.io/ktor-server-core/io.ktor.server.response/header.html) function:
+* Use the [`ApplicationResponse.header()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/header.html) function:
```kotlin
get("/") {
call.response.header(HttpHeaders.ETag, "7c876b7e")
}
```
-* Use functions dedicated to specifying concrete headers, for example, `ApplicationResponse.etag`, `ApplicationResponse.link`, and so on.
+* Use convenience functions for specific headers, such as `ApplicationResponse.etag`, `ApplicationResponse.link`, and others.
```kotlin
get("/") {
call.response.etag("7c876b7e")
}
```
-* To add a custom header, pass its name as a string value to any function mentioned above, for example:
+* Add custom headers by passing raw string names:
```kotlin
get("/") {
call.response.header("Custom-Header", "Some value")
}
```
-> To add the standard `Server` and `Date` headers into each response, install the [DefaultHeaders](server-default-headers.md) plugin.
+> To include standard `Server` and `Date` headers automatically, install the [DefaultHeaders](server-default-headers.md) plugin.
>
{type="tip"}
### Cookies {id="cookies"}
-To configure cookies sent in a response, use the [ApplicationResponse.cookies](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/cookies.html) property:
+
+To configure cookies sent in a response, use the [`ApplicationResponse.cookies`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/-application-response/cookies.html) property:
```kotlin
get("/") {
call.response.cookies.append("yummy_cookie", "choco")
}
```
-Ktor also provides the capability to handle sessions using cookies. You can learn more from the [Sessions](server-sessions.md) section.
+
+> Ktor also provides the ability to handle sessions using cookies. To learn more, see [](server-sessions.md).
## Redirects {id="redirect"}
-To generate a redirection response, call the [respondRedirect](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-redirect.html) function:
+
+To generate a redirect response, use the [`call.respondRedirect()`](https://api.ktor.io/ktor-server-core/io.ktor.server.response/respond-redirect.html) function:
+
```kotlin
get("/") {
call.respondRedirect("/moved", permanent = true)
diff --git a/topics/server-ssl.md b/topics/server-ssl.md
index 4097f15c6..79553b21c 100644
--- a/topics/server-ssl.md
+++ b/topics/server-ssl.md
@@ -101,7 +101,7 @@ following [properties](server-configuration-file.topic#predefined-properties):
```shell
```
- {style="block" src="snippets/ssl-engine-main/src/main/resources/application.conf" include-lines="1-2,4-5,18"}
+ {style="block" src="snippets/ssl-engine-main/src/main/resources/application.conf" include-lines="1-2,4-5,21"}
@@ -120,14 +120,14 @@ following [properties](server-configuration-file.topic#predefined-properties):
```shell
```
- {style="block" src="snippets/ssl-engine-main/src/main/resources/application.conf" include-lines="1,10-18"}
+ {style="block" src="snippets/ssl-engine-main/src/main/resources/application.conf" include-lines="1,10-21"}
```yaml
```
- {style="block" src="snippets/ssl-engine-main/src/main/resources/_application.yaml" include-lines="1,9-14"}
+ {style="block" src="snippets/ssl-engine-main/src/main/resources/_application.yaml" include-lines="1,9-17"}
diff --git a/topics/server-swagger-ui.md b/topics/server-swagger-ui.md
index f38fbc75e..9167b03f3 100644
--- a/topics/server-swagger-ui.md
+++ b/topics/server-swagger-ui.md
@@ -19,9 +19,13 @@
The SwaggerUI plugin allows you to generate Swagger UI for your project.
-Ktor allows you to generate and serve Swagger UI for your project based on the existing OpenAPI specification.
-With Swagger UI, you can visualize and interact with the API resources. You can serve an existing YAML or JSON
-specification, or generate one using the [OpenAPI extension](openapi-spec-generation.md) of the Ktor Gradle plugin.
+Ktor allows you to generate and serve Swagger UI for your project based on an OpenAPI specification.
+With Swagger UI, you can visualize and interact with your API endpoints directly from the browser.
+
+You can provide the OpenAPI specification in one of the following ways:
+
+* [Serve an existing YAML or JSON file](#static-openapi-file).
+* [Generate the specification at runtime using the OpenAPI compiler extension and runtime APIs](#generate-runtime-openapi-metadata).
## Add dependencies {id="add_dependencies"}
@@ -29,11 +33,19 @@ Serving Swagger UI requires adding the `%artifact_name%` artifact in the build s
+> In Ktor 3.4.0, the `SwaggerUI` plugin requires the `ktor-server-routing-openapi` dependency.
+> This was not an intentional breaking change and will be corrected in Ktor 3.4.1.
+> Add the dependency manually if you are using Ktor 3.4.0 to avoid runtime errors.
+>
+{style="warning"}
-## Configure Swagger UI {id="configure-swagger"}
+## Use a static OpenAPI file {id="static-openapi-file"}
-To serve Swagger UI, you need to call the [swaggerUI](%plugin_api_link%) method that creates a `GET` endpoint with Swagger UI
-at the `path` rendered from the OpenAPI specification placed at `swaggerFile`:
+To serve Swagger UI from an existing OpenAPI specification file, use the [`swaggerUI()`](%plugin_api_link%) function and
+specify the file location.
+
+The following example creates a `GET` endpoint at the `swagger` path and renders the Swagger UI from the provided
+OpenAPI specification file:
```kotlin
import io.ktor.server.plugins.swagger.*
@@ -44,25 +56,46 @@ routing {
}
```
-This method tries to look up the OpenAPI specification in the application resources.
-Otherwise, it tries to read the OpenAPI specification from the file system using `java.io.File`.
+The plugin first looks for the specification in the application resources. If not found, it attempts to load it from
+the file system using `java.io.File`.
+
+## Generate runtime OpenAPI metadata
-Optionally, you can customize Swagger UI inside the `swaggerUI` block.
-For example, you can use another Swagger UI version or apply a custom style.
+Instead of relying on a static file, you can generate the OpenAPI specification at runtime using metadata produced
+by the OpenAPI compiler plugin and route annotations:
```kotlin
+swaggerUI("/swaggerUI") {
+ info = OpenApiInfo("My API", "1.0")
+ source = OpenApiDocSource.Routing(ContentType.Application.Json) {
+ routingRoot.descendants()
+ }
+}
```
-{src="snippets/json-kotlinx-openapi/src/main/kotlin/com/example/Application.kt" include-lines="40,53-55,59"}
-You can now [run](server-run.md) the application and open the `/swagger` page to see the available endpoints, and test them.
+With this, you can access the generated OpenAPI documentation at the `/swaggerUI` path, reflecting the current state of
+the application.
+
+> For more information on the OpenAPI compiler extension and runtime APIs, see [](openapi-spec-generation.md).
+>
+{style="tip"}
+## Configure Swagger UI
+
+You can customize Swagger UI within the `swaggerUI {}` block, for example, by specifying a custom Swagger UI version:
+
+```kotlin
+```
+{src="snippets/json-kotlinx-openapi/src/main/kotlin/com/example/Application.kt" include-lines="40,53-55,59"}
## Configure CORS {id="configure-cors"}
-To make sure your API works nicely with Swagger UI, you need to set up a policy for [Cross-Origin Resource Sharing (CORS)](server-cors.md).
+To ensure Swagger UI can access your API endpoints correctly, you need to first configure [Cross-Origin Resource Sharing
+(CORS)](server-cors.md).
+
The example below applies the following CORS configuration:
-- `anyHost` enables cross-origin requests from any host;
-- `allowHeader` allows the `Content-Type` client header used in [content negotiation](server-serialization.md).
+* `anyHost` enables cross-origin requests from any host.
+* `allowHeader` allows the `Content-Type` client header used for [content negotiation](server-serialization.md).
```kotlin
```
diff --git a/topics/whats-new-330.md b/topics/whats-new-330.md
index c23e15d57..90eef8e96 100644
--- a/topics/whats-new-330.md
+++ b/topics/whats-new-330.md
@@ -176,7 +176,7 @@ This change unifies SSE handling across all client engines and addresses the lim
## Gradle plugin
### OpenAPI specification generation {id="openapi-spec-gen"}
-
+
Ktor 3.3.0 introduces an experimental OpenAPI generation feature via the Gradle plugin and a compiler plugin. This
allows you to generate OpenAPI specifications directly from your application code at build time.
@@ -189,9 +189,6 @@ It provides the following capabilities:
- Security, descriptions, deprecations, and external documentation links
- Infer request and response bodies from `call.receive()` and `call.respond()`.
-
-
-
#### Generate the OpenAPI specification
To generate the OpenAPI specification file from your Ktor routes and KDoc annotations, use the following command:
diff --git a/topics/whats-new-340.md b/topics/whats-new-340.md
new file mode 100644
index 000000000..24fe22e79
--- /dev/null
+++ b/topics/whats-new-340.md
@@ -0,0 +1,609 @@
+[//]: # (title: What's new in Ktor 3.4.0)
+
+
+
+_[Released: January 23, 2026](releases.md#release-details)_
+
+Ktor 3.4.0 delivers a wide range of enhancements across server, client, and tooling. Here are the highlights for this
+feature release:
+
+* [Zstd compression support](#zstd-compression-support)
+* [Http request lifecycle](#http-request-lifecycle)
+* [Runtime OpenAPI route annotations](#runtime-openapi-route-annotations)
+* [Duplex streaming for OkHttp](#duplex-streaming-for-okhttp)
+
+## Ktor Server
+
+### OAuth fallback for error handling
+
+Ktor 3.4.0 introduces a new [`fallback()`](https://api.ktor.io/ktor-server-auth/io.ktor.server.auth/-o-auth-authentication-provider/-config/fallback.html)
+function for the [OAuth](server-oauth.md) authentication provider.
+The fallback is invoked when the OAuth flow fails with `AuthenticationFailedCause.Error`, such as token exchange
+failures, network issues, or response parsing errors.
+
+Previously, you might have used `authenticate(optional = true)` on OAuth-protected routes to bypass OAuth failures.
+However, optional authentication only suppresses challenges when no credentials are provided and does not cover actual
+OAuth errors.
+
+The new `fallback()` function provides a dedicated mechanism for handling these scenarios. If the fallback does not
+handle the call, Ktor returns `401 Unauthorized`.
+
+To configure a fallback, define it inside the `oauth` block:
+
+```kotlin
+install(Authentication) {
+ oauth("login") {
+ client = ...
+ urlProvider = ...
+ providerLookup = { ... }
+ fallback = { cause ->
+ if (cause is OAuth2RedirectError) {
+ respondRedirect("/login-after-fallback")
+ } else {
+ respond(HttpStatusCode.Forbidden, cause.message)
+ }
+ }
+ }
+}
+```
+
+### Zstd compression support
+
+[Zstd](https://github.com/facebook/zstd) compression is now supported by the [Compression](server-compression.md)
+plugin.
+
+`Zstd` is a fast compression algorithm that offers high compression ratios and low compression times, and has a
+configurable compression level.
+
+To enable it, add the `ktor-server-compression-zstd` dependency to your project:
+```kotlin
+implementation("io.ktor:ktor-server-compression-zstd:$ktor_version")
+```
+
+Then, call the `zstd()` function inside the `install(Compression) {}` block with your desired configuration:
+
+```kotlin
+install(Compression) {
+ gzip()
+ deflate()
+ zstd(level = 3)
+ identity()
+}
+```
+
+### SSL trust store settings in a configuration file
+
+Ktor now allows you to configure additional [SSL settings](server-ssl.md#config-file) for the server using the
+application configuration file. You can specify a trust store, its corresponding password, and the list of enabled TLS
+protocols directly in your configuration.
+
+You define these settings under the `ktor.security.ssl` section:
+
+```kotlin
+// application.conf
+ktor {
+ security {
+ ssl {
+ // ...
+ trustStore = truststore.jks
+ trustStorePassword = foobar
+ enabledProtocols = ["TLSv1.2", "TLSv1.3"]
+ }
+ }
+}
+```
+
+From the code above:
+- `trustStore` – the path to the trust store file containing trusted certificates.
+- `trustStorePassword` – password for the trust store.
+- `enabledProtocols` – a list of allowed TLS protocols.
+
+### HTML fragments for partial responses
+
+Ktor now provides a new [`.respondHtmlFragment()`](https://api.ktor.io/ktor-server-html-builder/io.ktor.server.html/respond-html-fragment.html)
+function for sending partial HTML responses. This is useful when generating markup that does not require a full ``
+document, such as dynamic UI updates with tools like HTMX.
+
+The new API is part of the [HTML DSL](server-html-dsl.md) plugin and allows you to return HTML rooted in any element:
+
+```kotlin
+get("/books.html") {
+ call.respondHtmlFragment {
+ div("books") {
+ for (book in library.books()) {
+ bookItem()
+ }
+ }
+ }
+}
+```
+
+### HTTP request lifecycle
+
+The new [`HttpRequestLifecycle` plugin](server-http-request-lifecycle.md) allows you to cancel inflight HTTP requests when the client disconnects.
+This is useful when you need to cancel an inflight HTTP request for a long-running or resource-intensive request
+when the client disconnects.
+
+Enable this feature by installing the `HttpRequestLifecycle` plugin and setting `cancelCallOnClose = true`:
+
+```kotlin
+install(HttpRequestLifecycle) {
+ cancelCallOnClose = true
+}
+
+routing {
+ get("/long-process") {
+ try {
+ while (isActive) {
+ delay(10_000)
+ logger.info("Very important work.")
+ }
+ call.respond("Completed")
+ } catch (e: CancellationException) {
+ logger.info("Cleaning up resources.")
+ }
+ }
+}
+```
+
+When the client disconnects, the coroutine handling the request is canceled, and structured concurrency handles cleaning
+all resources. Any `launch` or `async` coroutines started by the request are also canceled.
+This is currently only supported for the `Netty` and `CIO` engine.
+
+### New method to respond with a resource
+
+The new [`call.respondResource()`](server-responses.md#resource) method works in a similar way to [`call.respondFile()`](server-responses.md#file),
+but accepts a resource instead of a file to respond with.
+
+To serve a single resource from the classpath, use `call.respondResource()` and specify the resource path:
+
+```kotlin
+routing {
+ get("/resource") {
+ call.respondResource("public/index.html")
+ }
+}
+```
+
+### Runtime OpenAPI route annotations
+
+
+
+Ktor 3.4.0 introduces the `ktor-server-routing-openapi` module, which allows you to attach OpenAPI metadata directly
+to routes using runtime annotations. These annotations are applied to routes at runtime and become part of the routing tree, making them available to
+OpenAPI-related tooling.
+
+The API is experimental and requires opting in using `@OptIn(ExperimentalKtorApi::class)`.
+
+To add metadata to a route at runtime, use the `.describe {}` extension function:
+
+```kotlin
+@OptIn(ExperimentalKtorApi::class)
+get("/messages") {
+ val query = call.parameters["q"]?.let(::parseQuery)
+ call.respond(messageRepository.getMessages(query))
+}.describe {
+ parameters {
+ query("q") {
+ description = "An encoded query"
+ required = false
+ }
+ }
+ responses {
+ HttpStatusCode.OK {
+ description = "A list of messages"
+ schema = jsonSchema>()
+ extension("x-sample-message", testMessage)
+ }
+ HttpStatusCode.BadRequest {
+ description = "Invalid query"
+ ContentType.Text.Plain()
+ }
+ }
+ summary = "get messages"
+ description = "Retrieves a list of messages."
+}
+```
+
+You can use this API as a standalone extension or in combination with Ktor's OpenAPI compiler plugin to automatically
+generate these calls. The [OpenAPI](server-openapi.md) and
+[SwaggerUI](server-swagger-ui.md) plugins also read this metadata when building the OpenAPI specification.
+
+> In Ktor 3.4.0, the `SwaggerUI` and `OpenAPI` plugins now require the `ktor-server-routing-openapi` dependency.
+> This was not an intentional breaking change and will be fixed in the 3.4.1 release.
+> If you use either plugin, add the dependency manually to avoid runtime errors.
+>
+{style="warning"}
+
+For more details and examples, see [](openapi-spec-generation.md#runtime-route-annotations).
+
+### API Key authentication
+
+The new [API Key authentication plugin](server-api-key-auth.md) allows you to secure server routes using a shared secret
+passed with each request, typically in an HTTP header.
+
+The `apiKey` provider integrates with Ktor’s [Authentication plugin](server-auth.md) and lets you validate incoming API
+keys using custom logic, customize the header name, and protect specific routes with standard `authenticate` blocks:
+
+```kotlin
+install(Authentication) {
+ apiKey("my-api-key") {
+ validate { apiKey ->
+ if (apiKey == "secret-key") {
+ UserIdPrincipal(apiKey)
+ } else {
+ null
+ }
+ }
+ }
+}
+
+routing {
+ authenticate {
+ get("/") {
+ val principal = call.principal()!!
+ call.respondText("Key: ${principal.key}")
+ }
+ }
+}
+```
+API Key authentication can be used for service-to-service communication and other scenarios where a lightweight
+authentication mechanism is sufficient.
+
+For more details and configuration options, see [](server-api-key-auth.md).
+
+## Core
+
+### Multiple header parsing
+
+The new [`Headers.getSplitValues()`](https://api.ktor.io/ktor-http/io.ktor.http/get-split-values.html) function
+simplifies working with headers that contain multiple values in a single line.
+
+The `getSplitValues()` function returns all values for the given header and splits them using the specified separator
+(`,` by default):
+
+```kotlin
+val headers = headers {
+ append("X-Multi-Header", "1, 2")
+ append("X-Multi-Header", "3")
+}
+
+val splitValues = headers.getSplitValues("X-Multi-Header")!!
+// ["1", "2", "3"]
+```
+By default, separators inside double-quoted strings are ignored, but you can change this by setting
+`splitInsideQuotes = true`:
+
+```kotlin
+val headers = headers {
+ append("X-Multi-Header", """a,"b,c",d""")
+}
+
+val forceSplit = headers.getSplitValues("X-Quoted", splitInsideQuotes = true)
+// ["a", "\"b", "c\"", "d"]
+```
+
+## Ktor Client
+
+### Authentication token cache control
+
+Prior to Ktor 3.4.0, applications using [Basic](client-basic-auth.md) and [Bearer authentication](client-bearer-auth.md)
+providers could continue sending outdated tokens or credentials after a user logged out or updated their authentication
+data. This happened because each provider internally caches the result of the `loadTokens()` function through
+an internal component responsible for storing loaded authentication tokens, and this cache remained active until
+manually cleared.
+
+Ktor 3.4.0 introduces new functions and configuration options that give you explicit and convenient control over token
+caching behavior.
+
+#### Accessing and clearing authentication tokens
+
+You can now access authentication providers directly from the client and clear their cached tokens when needed.
+
+To clear the token for a specific provider, use the `.clearToken()` function:
+
+```kotlin
+val provider = client.authProvider()
+provider?.clearToken()
+```
+
+Retrieve all authentication providers:
+
+```kotlin
+val providers = client.authProviders
+```
+
+To clear cached tokens from all providers that support token clearing (currently Basic and Bearer), use
+the `HttpClient.clearAuthTokens()` function:
+
+```kotlin
+ // Clears all cached auth tokens on logout
+fun logout() {
+ client.clearAuthTokens()
+ storage.deleteTokens()
+}
+
+// Clears cached auth tokens when credentials are updated
+fun updateCredentials(new: Credentials) {
+ storage.save(new)
+ client.clearAuthTokens() // Forces reload
+}
+```
+
+#### Configuring token cache behavior
+
+A new `cacheTokens` configuration option has been added to both Basic and Bearer authentication providers. This allows
+you to control whether tokens or credentials should be cached between requests.
+
+For example, you can disable caching when credentials are dynamically provided:
+
+```kotlin
+basic {
+ cacheTokens = false // Loads credentials on every request
+ credentials {
+ getCurrentUserCredentials()
+ }
+}
+```
+
+Disabling caching is especially useful when authentication data changes frequently or must always reflect the most
+recent state.
+
+### Duplex streaming for OkHttp
+
+The OkHttp client engine now supports duplex streaming, enabling clients to send request body data and receive response
+data simultaneously.
+
+Unlike regular HTTP calls where the request body must be fully sent before the response begins, duplex mode
+supports bidirectional streaming, allowing the client to send and receive data concurrently.
+
+Duplex streaming is available for HTTP/2 connections and can be enabled using the new `duplexStreamingEnabled` property
+in `OkHttpConfig`:
+
+```kotlin
+val client = HttpClient(OkHttp) {
+ engine {
+ duplexStreamingEnabled = true
+ config {
+ protocols(listOf(Protocol.H2_PRIOR_KNOWLEDGE))
+ }
+ }
+}
+```
+
+### Apache5 connection manager configuration
+
+The Apache5 engine now supports configuring the connection manager directly using the new [`configureConnectionManager {}`](https://api.ktor.io/ktor-client-apache5/io.ktor.client.engine.apache5/-apache5-engine-config/configure-connection-manager.html)
+function.
+
+This approach is recommended over the previous method using `customizeClient { setConnectionManager(...) }`. Using
+`customizeClient` would replace the Ktor-managed connection manager, potentially bypassing engine settings, timeouts,
+and other internal configuration.
+
+
+
+```kotlin
+val client = HttpClient(Apache5) {
+ engine {
+ customizeClient {
+ setConnectionManager(
+ PoolingAsyncClientConnectionManagerBuilder.create()
+ .setMaxConnTotal(10_000)
+ .setMaxConnPerRoute(1_000)
+ .build()
+ )
+ }
+ }
+}
+```
+
+```kotlin
+val client = HttpClient(Apache5) {
+ engine {
+ configureConnectionManager {
+ setMaxConnTotal(10_000)
+ setMaxConnPerRoute(1_000)
+ }
+ }
+}
+```
+
+
+
+The new `configureConnectionManager {}` function keeps Ktor in control while allowing you to adjust parameters such as
+maximum connections per route (`maxConnPerRoute`) and total maximum connections (`maxConnTotal`).
+
+### Dispatcher configuration for native client engines
+
+Native HTTP client engines (`Curl`, `Darwin`, and `WinHttp`) now respect the configured engine dispatcher and use
+`Dispatchers.IO` by default.
+
+The `dispatcher` property has always been available on client engine configurations, but native engines previously
+ignored it and always used `Dispatchers.Unconfined`. With this change, native engines use the configured dispatcher and
+default to `Dispatchers.IO` when none is specified, aligning their behavior with other Ktor client engines.
+
+You can explicitly configure the dispatcher as follows:
+
+```kotlin
+val client = HttpClient(Curl) {
+ engine {
+ dispatcher = Dispatchers.IO
+ }
+}
+```
+### HttpStatement execution using the engine dispatcher
+
+The `HttpStatement.execute {}` and `HttpStatement.body {}` blocks now run on the HTTP engine’s dispatcher
+instead of the caller’s coroutine context. This prevents accidental blocking when these blocks are invoked from the
+main thread.
+
+Previously, users had to manually switch dispatchers using `withContext` to avoid freezing the UI during I/O operations,
+such as writing a streaming response to a file. With this change, Ktor automatically dispatches these blocks to the
+engine’s coroutine context:
+
+
+
+```kotlin
+client.prepareGet("https://httpbin.org/bytes/$fileSize").execute { httpResponse ->
+ withContext(Dispatchers.IO) {
+ val channel: ByteReadChannel = httpResponse.body()
+ // Process and write data
+ }
+}
+```
+
+```kotlin
+client.prepareGet("https://httpbin.org/bytes/$fileSize").execute { httpResponse ->
+ val channel: ByteReadChannel = httpResponse.body()
+ // Process and write data
+}
+```
+
+
+### Plugin and default request configuration replacement
+
+Ktor client configuration now provides more control over replacing existing settings at runtime.
+
+#### Replace plugin configuration
+
+The new [`installOrReplace()`](https://api.ktor.io/ktor-client-core/io.ktor.client/-http-client-config/install-or-replace.html)
+function installs a client plugin or replaces its existing configuration if the plugin is already installed. This is
+useful when you need to reconfigure a plugin without manually removing it first.
+
+```kotlin
+val client = HttpClient {
+ installOrReplace(ContentNegotiation) {
+ json()
+ }
+}
+```
+
+In the above example, if `ContentNegotiation` is already installed, its configuration is replaced with the new one
+provided in the block.
+
+#### Replace default request configuration
+
+The [`defaultRequest()`](https://api.ktor.io/ktor-client-core/io.ktor.client.plugins/default-request.html) function now
+accepts an optional `replace` parameter (default is `false`). When set to `true`, the new configuration replaces any
+previously defined default request settings instead of merging with them.
+
+```kotlin
+val client = HttpClient {
+ defaultRequest(replace = true) {
+ // ...
+ }
+}
+```
+
+This allows you to explicitly override earlier default request configuration when composing or reusing client setups.
+
+### Shared source set support for `js` and `wasmJs` targets
+
+Ktor now supports [Kotlin’s shared `web` source set](https://kotlinlang.org/docs/whatsnew2220.html#shared-source-set-for-js-and-wasmjs-targets)
+in multiplatform projects, allowing you to share Ktor dependencies between `js` and `wasmJs` targets. This makes it
+easier to share web-specific client code, such as HTTP clients and engines, across JavaScript and Wasm/JS.
+
+In your
+build.gradle.kts
+file, you can declare Ktor dependencies in the `webMain` source set:
+
+```kotlin
+kotlin {
+ sourceSets {
+ webMain.dependencies {
+ implementation("io.ktor:ktor-client-js:%ktor_version%")
+ }
+ }
+}
+```
+
+You can then use APIs available to both `js` and `wasmJs` targets:
+
+```kotlin
+// src/webMain/kotlin/Main.kt
+
+actual fun createClient(): HttpClient = HttpClient(Js)
+```
+
+## I/O
+
+### Stream bytes from a `ByteReadChannel` to a `RawSink`
+
+You can now use the new [`ByteReadChannel.readTo()`](https://api.ktor.io/ktor-io/io.ktor.utils.io/read-to.html) function
+to read bytes from a channel and write them directly to a specified `RawSink`. This function simplifies handling large
+responses or file downloads without intermediate buffers or manual copying.
+
+The following example downloads a file and writes it to a new local file:
+
+```kotlin
+val client = HttpClient(CIO)
+val file = File.createTempFile("files", "index")
+val stream = file.outputStream().asSink()
+val fileSize = 100 * 1024 * 1024
+
+runBlocking {
+ client.prepareGet("https://httpbin.org/bytes/$fileSize").execute { httpResponse ->
+ val channel: ByteReadChannel = httpResponse.body()
+ channel.readTo(stream)
+ }
+}
+
+println("A file saved to ${file.path}")
+
+```
+
+## Gradle plugin
+
+### OpenAPI compiler extension
+
+Previously, the OpenAPI compiler plugin generated a complete, static OpenAPI document at build time. In Ktor 3.4.0, it
+instead generates code that provides OpenAPI metadata at runtime, which is consumed by the [OpenAPI](server-openapi.md)
+and [Swagger UI](server-swagger-ui.md) plugins when serving the specification.
+
+The dedicated `buildOpenApi` Gradle task has been removed. The compiler plugin is now automatically applied during
+regular builds, and changes to routes or annotations are reflected in the running server without requiring any
+additional generation steps.
+
+#### Configuration
+
+Configuration is still done using the `openApi {}` block inside the `ktor` Gradle extension. However, properties used
+to define global OpenAPI metadata, such as `title`, `version`, `description`, and `target`, have been deprecated and are
+ignored.
+
+Global OpenAPI metadata is now defined and resolved at runtime rather than during compilation.
+
+The compiler extension configuration is now limited to feature options that control how metadata is inferred and
+collected.
+
+For users migrating from the experimental preview in Ktor 3.3.0, the configuration has changed as follows:
+
+
+
+```kotlin
+// build.gradle.kts
+ktor {
+ @OptIn(OpenApiPreview::class)
+ openApi {
+ target = project.layout.projectDirectory.file("api.json")
+ title = "OpenAPI example"
+ version = "2.1"
+ summary = "This is a sample API"
+ }
+}
+```
+
+```kotlin
+// build.gradle.kts
+ktor {
+ openApi {
+ // Global control for the compiler plugin
+ enabled = true
+ // Enables and disables inferring details from call handler code
+ codeInferenceEnabled = true
+ // Toggles whether all routes should be analysed or only commented ones
+ onlyCommented = false
+ }
+}
+```
+
+
diff --git a/v.list b/v.list
index e43ebcf9c..35b560645 100644
--- a/v.list
+++ b/v.list
@@ -4,8 +4,8 @@
-
-
+
+
|