Ktor 3.6.0 Help

Type-safe requests

Ktor provides the Resources plugin for making type-safe client requests. To achieve this, you define classes that represent server endpoints and annotate them with the @Resource keyword.

Resource classes use kotlinx.serialization to convert their properties to path and query parameters.

Add dependencies

Add kotlinx.serialization

The Resources plugin relies on kotlinx.serialization. Enable the Kotlin serialization plugin as described in the kotlinx.serialization setup guide.

Add Resources dependencies

To use Resources, add the ktor-client-resources artifact to your build script:

implementation("io.ktor:ktor-client-resources:$ktor_version")
implementation "io.ktor:ktor-client-resources:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-client-resources-jvm</artifactId> <version>${ktor_version}</version> </dependency>

Install Resources

To install the Resources plugin, pass it to the install function in the client configuration block:

import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.resources.* //... val client = HttpClient(CIO) { install(Resources) }

Create resource classes

Each resource class should have the @Resource annotation. Below, we'll take a look at several examples of resource classes - defining a single path segment, query and path parameters, and so on.

Resource URL

The example below shows how to define the Articles class that specifies a resource responding on the /articles path.

import io.ktor.resources.* @Resource("/articles") class Articles()

Resources with a query parameter

The Articles class below has the sort string property that acts as a query parameter and allows you to define a resource responding on the following path with the sort query parameter: /articles?sort=new.

@Resource("/articles") class Articles(val sort: String? = "new")

Resources with nested classes

You can nest classes to create resources that contain several path segments. Note that in this case nested classes should have a property with an outer class type. The example below shows a resource responding on the /articles/new path.

@Resource("/articles") class Articles() { @Resource("new") class New(val parent: Articles = Articles()) }

Resources with a path parameter

The example below demonstrates how to add the nested {id} integer path parameter that matches a path segment and captures it as a parameter named id.

@Resource("/articles") class Articles() { @Resource("{id}") class Id(val parent: Articles = Articles(), val id: Long) }

As an example, this resource can be used to respond on /articles/12.

Example: A resource for CRUD operations

The following example creates the Articles resource for CRUD operations:

@Resource("/articles") class Articles() { @Resource("new") class New(val parent: Articles = Articles()) @Resource("{id}") class Id(val parent: Articles = Articles(), val id: Long) { @Resource("edit") class Edit(val parent: Id) } }

This resource can be used to list all articles, post a new article, and edit an existing one.

The next section shows how to make type-safe requests using this resource.

Make type-safe requests

To make a request to a typed resource, pass a resource class instance to a request function, such as request(), get(), post(), or put().

The following example makes a request to the /articles path:

@Resource("/articles") class Articles() fun main() { runBlocking { val client = HttpClient(CIO) { install(Resources) // ... } val getAllArticles = client.get(Articles()) } }

The following example makes typed requests to the Articles resource created in Example: A resource for CRUD operations.

fun main() { defaultServer(Application::module).start() runBlocking { val client = HttpClient(CIO) { install(Resources) defaultRequest { host = "0.0.0.0" port = 8080 url { protocol = URLProtocol.HTTP } } } val getAllArticles = client.get(Articles()) val newArticle = client.get(Articles.New()) val postArticle = client.post(Articles()) { setBody("Article content") } val getArticle = client.get(Articles.Id(id = 12)) val editArticlePage = client.get(Articles.Id.Edit(Articles.Id(id = 12))) val putArticle = client.put(Articles.Id(id = 12)) { setBody("New article content") } val deleteArticle = client.delete(Articles.Id(id = 12)) }

The defaultRequest() function specifies a default URL for all requests.

27 August 2026