Docs / Programming & Development / Kotlin Ktor Application

Kotlin Ktor Application

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 328 views · 4 min read

Ktor is a lightweight, asynchronous web framework built by JetBrains for Kotlin. It leverages Kotlin coroutines for non-blocking I/O and offers a clean DSL for routing, serialization, and authentication. This guide covers building and deploying a Ktor application on a VPS.

Project Setup

# Use the Ktor Project Generator at https://start.ktor.io
# Or create manually with Gradle

mkdir ktor-app && cd ktor-app

# build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.0"
    id("io.ktor.plugin") version "2.3.12"
    kotlin("plugin.serialization") version "2.0.0"
}

application {
    mainClass.set("com.example.ApplicationKt")
}

dependencies {
    implementation("io.ktor:ktor-server-core-jvm")
    implementation("io.ktor:ktor-server-netty-jvm")
    implementation("io.ktor:ktor-server-content-negotiation-jvm")
    implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
    implementation("io.ktor:ktor-server-cors-jvm")
    implementation("io.ktor:ktor-server-compression-jvm")
    implementation("io.ktor:ktor-server-call-logging-jvm")
    implementation("io.ktor:ktor-server-status-pages-jvm")
    implementation("org.jetbrains.exposed:exposed-core:0.53.0")
    implementation("org.jetbrains.exposed:exposed-dao:0.53.0")
    implementation("org.jetbrains.exposed:exposed-jdbc:0.53.0")
    implementation("org.postgresql:postgresql:42.7.3")
    implementation("com.zaxxer:HikariCP:5.1.0")
    implementation("ch.qos.logback:logback-classic:1.5.6")
}

Application Code

// src/main/kotlin/com/example/Application.kt
package com.example

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.plugins.compression.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.http.*
import kotlinx.serialization.Serializable

@Serializable
data class User(val id: Int, val name: String, val email: String)

@Serializable
data class CreateUser(val name: String, val email: String)

@Serializable
data class ErrorResponse(val error: String)

fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        configurePlugins()
        configureRouting()
    }.start(wait = true)
}

fun Application.configurePlugins() {
    install(ContentNegotiation) { json() }
    install(CORS) { anyHost(); allowHeader(HttpHeaders.ContentType) }
    install(Compression) { gzip() }
    install(CallLogging)
    install(StatusPages) {
        exception<Throwable> { call, cause ->
            call.respond(HttpStatusCode.InternalServerError,
                ErrorResponse(cause.localizedMessage ?: "Unknown error"))
        }
    }
}

fun Application.configureRouting() {
    routing {
        get("/health") {
            call.respond(mapOf("status" to "healthy"))
        }

        route("/api/users") {
            get {
                val users = listOf(
                    User(1, "Alice", "alice@example.com"),
                    User(2, "Bob", "bob@example.com")
                )
                call.respond(users)
            }

            post {
                val input = call.receive<CreateUser>()
                val user = User(3, input.name, input.email)
                call.respond(HttpStatusCode.Created, user)
            }

            get("/{id}") {
                val id = call.parameters["id"]?.toIntOrNull()
                    ?: return@get call.respond(HttpStatusCode.BadRequest,
                        ErrorResponse("Invalid ID"))
                call.respond(User(id, "Alice", "alice@example.com"))
            }
        }
    }
}

Building and Deploying

# Build fat JAR
./gradlew buildFatJar
# Output: build/libs/ktor-app-all.jar

# Or build with Ktor plugin
./gradlew installDist
# Output: build/install/ktor-app/

# Copy to VPS
scp build/libs/ktor-app-all.jar user@vps:/opt/ktor-app/

# Run on VPS
java -jar /opt/ktor-app/ktor-app-all.jar

Docker Deployment

FROM gradle:8-jdk21 AS builder
WORKDIR /app
COPY . .
RUN gradle buildFatJar --no-daemon

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*-all.jar app.jar
USER nobody
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Systemd Service

[Unit]
Description=Ktor Application
After=network.target

[Service]
Type=simple
User=appuser
ExecStart=/usr/bin/java -jar /opt/ktor-app/ktor-app-all.jar
Environment=JAVA_OPTS=-Xmx512m
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Database with Exposed ORM

// Database configuration using Exposed
object Users : Table() {
    val id = integer("id").autoIncrement()
    val name = varchar("name", 100)
    val email = varchar("email", 255).uniqueIndex()
    override val primaryKey = PrimaryKey(id)
}

fun initDatabase() {
    Database.connect(
        HikariDataSource(HikariConfig().apply {
            jdbcUrl = System.getenv("DATABASE_URL")
            maximumPoolSize = 20
            isAutoCommit = false
        })
    )
    transaction { SchemaUtils.create(Users) }
}

// In routes
get("/api/users") {
    val users = transaction {
        Users.selectAll().map {
            User(it[Users.id], it[Users.name], it[Users.email])
        }
    }
    call.respond(users)
}

Summary

Ktor provides an idiomatic Kotlin experience for building web APIs with coroutine-based concurrency. Its plugin architecture keeps applications lightweight — you only include what you need. Combined with Kotlin serialization for type-safe JSON handling and Exposed for database access, Ktor offers a productive, performant stack for JVM-based web services. Deployment follows the standard JVM pattern: build a fat JAR, deploy with systemd, and proxy with Nginx.

Was this article helpful?