1. Overview

Ktor is a framework for building asynchronous servers and clients in connected systems using the powerful Kotlin programming language. It facilitates developing a standalone application with embedded servers.

In this tutorial, we’re going to explore how to create a standalone server application using Ktor.

2. Setting up a Ktor Application

Let’s start by setting up the Ktor project. We’ll be using Gradle which is the recommended and easy to use approach. Gradle can be installed by following the instructions provided on the Gradle site.

Create the build.gradle file:

val ktorVersion = "2.3.5"
val logbackVersion = "1.4.14"
val kotlinTestUnit = "1.9.10"
plugins {
    kotlin("jvm") version "1.9.10"
    id("io.ktor.plugin") version "2.3.5"
repositories {

dependencies {
    implementation("io.ktor", "ktor-server-netty", ktorVersion)
    implementation("ch.qos.logback", "logback-classic", logbackVersion)

kotlin {

task runServer(type: JavaExec) {
    main = 'APIServer'
    classpath = sourceSets.main.runtimeClasspath

We’ve imported Ktor and the Ktor netty server package. Netty is the embedded server we’ll use in this example.

3. Building the Server

We create our application by adding code to the source folder src/main/kotlin.

Here, we create the file APIServer.kt with the main method:

fun main(args: Array<String>) {


Next, we create and start the embedded Netty server:

embeddedServer(Netty, 8080) {
}.start(wait = true)

It will create and start the server at port 8080. We have set wait=true in the start() method to listen for connections.

4. Building the API

Let’s add the API. To handle HTTP requests, Ktor provides the Routing feature.

We activate the Routing feature with an install block where we can define routes for specific paths and HTTP methods:

val jsonResponse = """{
    "id": 1,
    "task": "Pay waterbill",
    "description": "Pay water bill today",

embeddedServer(Netty, 8080) {
  install(Routing) {
      get("/todo") {
          call.respondText(jsonResponse, ContentType.Application.Json)
}.start(wait = true)

In this example, the server will handle a GET request for the path /todo and will reply with a todo JSON objectWe’ll learn more about installing features in the section Installing Features.

5. Running the Server

To run the server, we need a run task in Gradle:

task runServer(type: JavaExec) {
    main = 'APIServer'
    classpath = sourceSets.main.runtimeClasspath

To start the server, we call this task:

./gradlew runServer

Out API can then be accessed via http://localhost:8080/todo.

6. Installing Features

A Ktor application typically consists of a series of features. We could think of features as functionality that is injected into the request and response pipeline.

Using the DefaultHeaders feature, we can add headers to every outgoing response. Routing is another feature which allows us to define routes to handle requests, etc.

We can also develop our features and install them.

Let’s take a look by adding a custom header to each request by installing the DefaultHeaders feature:

install(DefaultHeaders) {
    header("X-Developer", "Baeldung")

Similarly, we can override the default headers set by the Ktor framework itself:

install(DefaultHeaders) {
    header(HttpHeaders.Server, "My Server")

The list of available default headers can be found in the class io.ktor.features.DefaultHeaders. 

7. Serving JSON

Building stringified JSON manually isn’t easy. Ktor provides a feature to serve data objects as JSON using Gson.

Let’s add the Gson dependency in our build.gradle:

compile "io.ktor:ktor-gson:$ktor_version"

For example, we use a data object with the name Author:

data class Author(val name: String, val website: String)

Next, we install the gson feature:

install(ContentNegotiation) {
   gson {

Finally, let’s add a route to the server that serves an author object as JSON:

get("/author") {
    val author = Author("baeldung", "baeldung.com")

The author API will serve the author data object as JSON.

8. Adding Controllers

To understand how to handle multiple HTTP action requests let’s create a TODO application that allows the user to add, delete, view, and list TODO items.

We’ll start by adding a Todo data class:

data class ToDo(var id: Int, val name: String, val description: String, val completed: Boolean)

Then we create an ArrayList to hold multiple Todo items:

val toDoList = ArrayList<ToDo>();

Next, we add the controllers to handle POST, DELETE and GET requests:

routing() {
    route("/todo") {
        post {
            var toDo = call.receive<ToDo>();
            toDo.id = toDoList.size;

        delete("/{id}") {
        get("/{id}") {
        get {

We’ve added a todo route and then mapped the different HTTP verbs request to that endpoint.

9. Conclusion

In this article, we’ve learned how to create a Kotlin server application with the Ktor framework.

We’ve built a small server application in a few minutes without using any boilerplate code.

As always, the code samples can be found over on GitHub.