A multiplatform asynchronous framework for creating microservices, web applications, and HTTP clients written in Kotlin from the ground up
—
URL-encoded and multipart form data support for form submissions and file uploads with type-safe DSL builders.
URL-encoded form data content for application/x-www-form-urlencoded submissions.
/**
* Content for URL-encoded form data submissions
*/
class FormDataContent(
val formData: Parameters
) : OutgoingContent.ByteArrayContent() {
/** Content length in bytes */
override val contentLength: Long
/** Content type for URL-encoded form data */
override val contentType: ContentType = ContentType.Application.FormUrlEncoded
/**
* Get form data as byte array
* @return URL-encoded form data bytes
*/
override fun bytes(): ByteArray
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
val client = HttpClient()
// Create form data content
val formData = Parameters.build {
append("username", "john.doe")
append("password", "secretpassword")
append("remember", "true")
}
val response = client.post("https://api.example.com/login") {
setBody(FormDataContent(formData))
}Multipart form data content for file uploads and complex form submissions.
/**
* Content for multipart form data submissions
*/
class MultiPartFormDataContent(
parts: List<PartData>,
val boundary: String = generateBoundary(),
override val contentType: ContentType = ContentType.MultiPart.FormData
.withParameter("boundary", boundary)
) : OutgoingContent.WriteChannelContent() {
/** Content length, null for streaming */
override var contentLength: Long? = null
/**
* Write multipart content to channel
* @param channel Output channel for writing data
*/
override suspend fun writeTo(channel: ByteWriteChannel)
}Convenient functions for submitting forms without manually creating content objects.
/**
* Submit URL-encoded form data
* @param url Target URL
* @param formParameters Form parameters to submit
* @param encodeInQuery Whether to encode parameters in URL query string
* @param block Additional request configuration
* @return HttpResponse from server
*/
suspend fun HttpClient.submitForm(
url: String,
formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse
/**
* Submit multipart form data with binary data
* @param url Target URL
* @param formData List of form parts including files
* @param block Additional request configuration
* @return HttpResponse from server
*/
suspend fun HttpClient.submitFormWithBinaryData(
url: String,
formData: List<PartData>,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponseUsage Examples:
import io.ktor.client.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
val client = HttpClient()
// Submit simple form
val response = client.submitForm(
url = "https://api.example.com/contact",
formParameters = Parameters.build {
append("name", "John Doe")
append("email", "john@example.com")
append("message", "Hello from Ktor!")
}
)
// Submit form with file upload
val formData = formData {
append("name", "John Doe")
append("file", fileBytes, Headers.build {
append(HttpHeaders.ContentDisposition, "filename=\"document.pdf\"")
append(HttpHeaders.ContentType, "application/pdf")
})
}
val uploadResponse = client.submitFormWithBinaryData(
url = "https://api.example.com/upload",
formData = formData
)Type-safe DSL for building multipart form data with support for text fields, files, and binary data.
/**
* Build form data using DSL
* @param block Form building block
* @return List of form parts
*/
fun formData(block: FormBuilder.() -> Unit): List<PartData>
/**
* Builder for constructing multipart form data
*/
class FormBuilder {
/**
* Append text field to form
* @param key Field name
* @param value Field value
*/
fun append(key: String, value: String)
/**
* Append text field with custom headers
* @param key Field name
* @param value Field value
* @param headers Custom headers for the part
*/
fun append(key: String, value: String, headers: Headers)
/**
* Append binary data to form
* @param key Field name
* @param value Binary data as byte array
* @param headers Custom headers for the part
*/
fun append(key: String, value: ByteArray, headers: Headers = Headers.Empty)
/**
* Append data from Input provider
* @param key Field name
* @param provider Function that provides Input stream
* @param headers Custom headers for the part
*/
fun append(
key: String,
provider: () -> Input,
headers: Headers = Headers.Empty
)
/**
* Append data from ByteReadChannel provider
* @param key Field name
* @param provider Function that provides ByteReadChannel
* @param headers Custom headers for the part
*/
fun append(
key: String,
provider: () -> ByteReadChannel,
headers: Headers = Headers.Empty
)
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.utils.io.*
import java.io.File
val client = HttpClient()
// Build complex form with various field types
val formData = formData {
// Text fields
append("username", "john.doe")
append("description", "User profile update")
// File upload from byte array
append("avatar", avatarBytes, Headers.build {
append(HttpHeaders.ContentDisposition, "filename=\"avatar.jpg\"")
append(HttpHeaders.ContentType, "image/jpeg")
})
// File upload from File (JVM)
append("document", File("document.pdf").readBytes(), Headers.build {
append(HttpHeaders.ContentDisposition, "filename=\"document.pdf\"")
append(HttpHeaders.ContentType, "application/pdf")
})
// Streaming data
append("data", { ByteReadChannel(largeDataBytes) }, Headers.build {
append(HttpHeaders.ContentDisposition, "filename=\"data.bin\"")
append(HttpHeaders.ContentType, "application/octet-stream")
})
}
val response = client.submitFormWithBinaryData(
url = "https://api.example.com/profile/update",
formData = formData
) {
header("Authorization", "Bearer $token")
}Data classes representing different types of form parts.
/**
* Base class for form parts
*/
sealed class PartData {
/** Headers for this part */
abstract val headers: Headers
/** Release resources associated with this part */
abstract fun dispose()
}
/**
* Form part containing text data
*/
class PartData.FormItem(
val value: String,
override val headers: Headers
) : PartData()
/**
* Form part containing binary data
*/
class PartData.BinaryItem(
val provider: () -> Input,
override val headers: Headers
) : PartData()
/**
* Form part containing binary channel data
*/
class PartData.BinaryChannelItem(
val provider: () -> ByteReadChannel,
override val headers: Headers
) : PartData()
/**
* Form part containing file data
*/
class PartData.FileItem(
val provider: () -> Input,
override val headers: Headers
) : PartData()Utilities for working with Content-Disposition headers in form data.
/**
* Content-Disposition header utilities
*/
object ContentDisposition {
/**
* Create Content-Disposition header for form field
* @param name Field name
* @return Content-Disposition header value
*/
fun formData(name: String): String = "form-data; name=\"$name\""
/**
* Create Content-Disposition header for file field
* @param name Field name
* @param filename File name
* @return Content-Disposition header value
*/
fun formData(name: String, filename: String): String =
"form-data; name=\"$name\"; filename=\"$filename\""
/**
* Create Content-Disposition header for attachment
* @param filename File name
* @return Content-Disposition header value
*/
fun attachment(filename: String): String = "attachment; filename=\"$filename\""
}Usage Examples:
import io.ktor.client.request.forms.*
import io.ktor.http.*
val formData = formData {
// Simple field
append("message", "Hello World", Headers.build {
append(HttpHeaders.ContentDisposition, ContentDisposition.formData("message"))
})
// File field
append("upload", fileBytes, Headers.build {
append(HttpHeaders.ContentDisposition,
ContentDisposition.formData("upload", "document.pdf"))
append(HttpHeaders.ContentType, "application/pdf")
})
}Utilities for building URL-encoded form parameters.
/**
* Parameters interface for form data
*/
interface Parameters : StringValues {
companion object {
/** Empty parameters instance */
val Empty: Parameters
/**
* Build parameters using DSL
* @param block Building block
* @return Built parameters
*/
fun build(block: ParametersBuilder.() -> Unit): Parameters
}
}
/**
* Builder for constructing parameters
*/
interface ParametersBuilder : StringValuesBuilder {
/**
* Append parameter value
* @param name Parameter name
* @param value Parameter value
*/
fun append(name: String, value: String)
/**
* Append multiple values for parameter
* @param name Parameter name
* @param values Parameter values
*/
fun appendAll(name: String, values: Iterable<String>)
/**
* Set parameter value (replaces existing)
* @param name Parameter name
* @param value Parameter value
*/
fun set(name: String, value: String)
/**
* Remove parameter
* @param name Parameter name to remove
*/
fun remove(name: String)
/**
* Clear all parameters
*/
fun clear()
/**
* Build final parameters
* @return Immutable parameters instance
*/
fun build(): Parameters
}Usage Examples:
import io.ktor.client.request.forms.*
import io.ktor.http.*
// Build parameters for form submission
val parameters = Parameters.build {
append("search", "kotlin")
append("category", "programming")
append("tags", "multiplatform")
append("tags", "coroutines") // Multiple values for same key
set("limit", "50") // Replace any existing value
}
val response = client.submitForm(
url = "https://api.example.com/search",
formParameters = parameters
)Additional support for File objects on JVM platform.
/**
* JVM-specific file content support
*/
class LocalFileContent(
private val file: File
) : OutgoingContent.ReadChannelContent() {
override val contentLength: Long = file.length()
override fun readFrom(): ByteReadChannel
}
/**
* Set request body from File (JVM only)
* @param file File to upload
*/
fun HttpRequestBuilder.setBody(file: File)
/**
* Append file to form data (JVM only)
* @param key Field name
* @param file File to append
* @param headers Custom headers
*/
fun FormBuilder.appendFile(
key: String,
file: File,
headers: Headers = Headers.Empty
)Usage Examples (JVM only):
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import java.io.File
val client = HttpClient()
// Upload file directly
val file = File("document.pdf")
val response = client.post("https://api.example.com/upload") {
setBody(file)
}
// Include file in form
val formWithFile = formData {
append("title", "Important Document")
appendFile("document", file, Headers.build {
append(HttpHeaders.ContentType, "application/pdf")
})
}Form data related types:
/**
* String values interface for parameters
*/
interface StringValues {
val caseInsensitiveName: Boolean
fun get(name: String): String?
fun getAll(name: String): List<String>?
fun names(): Set<String>
fun isEmpty(): Boolean
fun entries(): Set<Map.Entry<String, List<String>>>
operator fun contains(name: String): Boolean
fun forEach(body: (String, List<String>) -> Unit)
}
/**
* Headers interface extending StringValues
*/
interface Headers : StringValues {
companion object {
val Empty: Headers
fun build(block: HeadersBuilder.() -> Unit): Headers
}
}
/**
* Headers builder interface
*/
interface HeadersBuilder : StringValuesBuilder {
fun append(name: String, value: String)
fun appendAll(name: String, values: Iterable<String>)
fun set(name: String, value: String)
fun remove(name: String)
fun clear()
fun build(): Headers
}
/**
* Generate boundary for multipart content
* @return Random boundary string
*/
fun generateBoundary(): String
/**
* Input stream interface for reading data
*/
interface Input : Closeable {
fun readByte(): Byte
fun readBytes(count: Int): ByteArray
fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor