Ktor 3.6.0 Help

Handling requests

Ktor allows you to handle incoming requests and send responses from route handlers.

Each route handler provides an ApplicationCall through the call property. An ApplicationCall represents a single HTTP exchange and provides access to both the incoming request and the outgoing response.

Within a route handler, you can use ApplicationCall to perform the following:

General request information

You can access request data through the call.request property. This returns an ApplicationRequest instance, which provides access to low-level HTTP request information.

For example, you can retrieve the request URI in a GET request handler using call.request.uri:

routing { get("/") { val uri = call.request.uri call.respondText("Request uri: $uri") } }

The call.respondText() function sends a plain text response back to the client.

Headers

To access all HTTP request headers, use the ApplicationRequest.headers property.

For convenience, Ktor also provides dedicated extension functions for accessing commonly used headers, such as .acceptEncoding(), .contentType(), and .cacheControl().

Cookies

To access cookies sent with the request, use the ApplicationRequest.cookies property.

Connection details

Use the ApplicationRequest.local property to access connection details such as the host, port, and scheme.

X-Forwarded- headers

To collect information about a request passed through an HTTP proxy or a load balancer, install the Forwarded headers plugin. You can then access this information through the ApplicationRequest.origin property.

Path parameters

When handling requests, you can retrieve path parameter values using the ApplicationCall.parameters property.

For example, call.parameters["login"] returns "admin" for a request to /user/admin:

get("/user/{login}") { if (call.parameters["login"] == "admin") { // ... } }

Query parameters

To retrieve parameters of a URL query string, use the ApplicationRequest.queryParameters property.

The following example accesses the price query parameter from a request made to /products?price=asc:

get("/products") { if (call.request.queryParameters["price"] == "asc") { // Show products from the lowest price to the highest } }

You can also get the entire query string using the ApplicationRequest.queryString() function.

Required request parameters

When handling requests, it is common to extract values from path parameters, query parameters, headers, or cookies and validate that they are present before continuing request processing.

Instead of manually checking for missing values in every route handler, Ktor provides the following helper functions that simplify accessing required request data:

Each function returns a non-null value or throws MissingRequestParameterException if the requested value is missing.

post("/checkout/{cartId}") { val userId = call.requireCookie("userId") val cartId = call.requirePathParameter("cartId") val amount = call.requireQueryParameter("amount").toLong() // Business logic }

Body contents

To access the request body, use Ktor's receive functions. The appropriate function depends on whether you need raw content, a deserialized object, form parameters, or multipart data.

Raw payload

To access the raw body payload and parse it manually, use the ApplicationCall.receive() function that accepts a type of payload to be received.

Suppose a client sends the following HTTP request:

POST http://localhost:8080/text Content-Type: text/plain Hello, world!

You can receive the request body as a String, ByteArray, or ByteReadChannel.

String

To receive a request body as text, use the .receive<String>() or .receiveText() function:

post("/text") { val text = call.receiveText() call.respondText(text) }

ByteArray

To receive the body of a request as a byte array, use the .receive<ByteArray>() function:

post("/bytes") { val bytes = call.receive<ByteArray>() call.respond(String(bytes)) }

ByteReadChannel

To read the body asynchronously as a ByteReadChannel, use the .receive<ByteReadChannel>() or .receiveChannel() function:

post("/channel") { val readChannel = call.receiveChannel() val text = readChannel.readRemaining().readString() call.respondText(text) }

You can also use a ByteReadChannel to upload a file:

post("/upload") { val file = File("uploads/ktor_logo.png") call.receiveChannel().copyAndClose(file.writeChannel()) call.respondText("A file is uploaded") }

Objects

Ktor provides the ContentNegotiation plugin to negotiate the media type of request and deserialize content to an object of a required type.

To receive and convert content for a request, use the ApplicationCall.receive() function with the expected type:

post("/customer") { val customer = call.receive<Customer>() customerStorage.add(customer) call.respondText("Customer stored correctly", status = HttpStatusCode.Created) }

If the request content can deserialize to null, use a nullable type argument:

val customer = call.receive<Customer?>()

Form parameters

You can receive form parameters sent with both x-www-form-urlencoded and multipart/form-data types using the .receiveParameters() function.

For example, suppose a client sends the following request:

POST http://localhost:8080/signup Content-Type: application/x-www-form-urlencoded username=JetBrains&email=example@jetbrains.com&password=foobar&confirmation=foobar

You can access parameter values in code as follows:

post("/signup") { val formParameters = call.receiveParameters() val username = formParameters["username"].toString() call.respondText("The '$username' account is created") }

Multipart form data

To receive a file sent as a part of a multipart request, use the .receiveMultipart() function.

Multipart request data is processed sequentially, so you can't directly access a specific part of it. Each part can represent a form field, a file, or other binary content, so handle each type separately.

The following example receives a form field and a file, then saves the file to the local file system:

import io.ktor.server.application.* import io.ktor.http.content.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.util.cio.* import io.ktor.utils.io.* import java.io.File fun Application.main() { routing { post("/upload") { var fileDescription = "" var fileName = "" val multipartData = call.receiveMultipart(formFieldLimit = 1024 * 1024 * 100) multipartData.forEachPart { part -> when (part) { is PartData.FormItem -> { fileDescription = part.value } is PartData.FileItem -> { fileName = part.originalFileName as String val file = File("uploads/$fileName") part.provider().copyAndClose(file.writeChannel()) } else -> {} } part.dispose() } call.respondText("$fileDescription is uploaded to 'uploads/$fileName'") } } }

Default file size limit

By default, binary and file parts are limited to 50MiB. If a part exceeds this limit, Ktor throws an IOException.

To override the default limit for a call, pass the formFieldLimit parameter to the .receiveMultipart() function:

val multipartData = call.receiveMultipart(formFieldLimit = 1024 * 1024 * 100)

This example sets the limit to 100 MiB.

Form fields

PartData.FormItem represents a form field. You can access its value through the value property:

when (part) { is PartData.FormItem -> { fileDescription = part.value } }

File uploads

PartData.FileItem represents an uploaded file. You can handle file uploads as byte streams. Use the .provider() function to access the file content as a ByteReadChannel and stream it to a destination:

when (part) { is PartData.FileItem -> { fileName = part.originalFileName as String val file = File("uploads/$fileName") part.provider().copyAndClose(file.writeChannel()) } }

With the .copyAndClose() function, you write the file content to the specified destination while ensuring proper resource cleanup.

If the request includes a Content-Length header value, you can use it to inspect the size of the complete request body:

post("/upload") { val contentLength = call.request.header(HttpHeaders.ContentLength) // ... }

For multipart requests, Content-Length represents the entire multipart body, not the size of an individual uploaded file.

Resource cleanup

Once form processing is complete, dispose of each multipart part using the .dispose() function to free its resources:

part.dispose()
03 September 2026