You decided to build a backend with Kotlin. Good choice. But now you face another decision: Ktor or Spring Boot?

Both are great frameworks. Both support Kotlin. But they are very different in philosophy, design, and use cases.

Let’s compare them honestly.

The Big Picture

Ktor is a lightweight, modular framework built by JetBrains. It is Kotlin-native, coroutine-based, and you add features as plugins.

Spring Boot is a full-featured, batteries-included framework from VMware (now Broadcom). It started as a Java framework and added Kotlin support later.

Ktor Philosophy:           Spring Boot Philosophy:
┌──────────────────┐       ┌──────────────────┐
│  Start minimal   │       │  Start with      │
│  Add what you    │       │  everything      │
│  need            │       │  Remove what you │
│                  │       │  don't need      │
└──────────────────┘       └──────────────────┘

Side-by-Side Comparison

Hello World

Ktor:

fun main() {
    embeddedServer(Netty, port = 8080) {
        routing {
            get("/hello") {
                call.respondText("Hello, World!")
            }
        }
    }.start(wait = true)
}

Spring Boot:

@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}

@RestController
class HelloController {
    @GetMapping("/hello")
    fun hello(): String = "Hello, World!"
}

Ktor uses a DSL. Spring Boot uses annotations. Both work, but they feel very different.

Route Definition

Ktor:

fun Application.configureRouting() {
    routing {
        route("/api/users") {
            get { /* list users */ }
            get("/{id}") { /* get by id */ }
            post { /* create user */ }
            put("/{id}") { /* update user */ }
            delete("/{id}") { /* delete user */ }
        }
    }
}

Spring Boot:

@RestController
@RequestMapping("/api/users")
class UserController(
    private val userService: UserService
) {
    @GetMapping
    fun listUsers(): List<User> = userService.findAll()

    @GetMapping("/{id}")
    fun getUser(@PathVariable id: Long): User = userService.findById(id)

    @PostMapping
    fun createUser(@RequestBody user: User): User = userService.create(user)

    @PutMapping("/{id}")
    fun updateUser(@PathVariable id: Long, @RequestBody user: User): User =
        userService.update(id, user)

    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable id: Long) = userService.delete(id)
}

Ktor routes are functions inside a DSL block. Spring Boot routes are methods in a class with annotations. Ktor feels more like writing Kotlin code. Spring Boot feels more like decorated Java.

Dependency Injection

Ktor: No built-in DI. You choose your own (Koin, Kodein, or manual).

// With Koin
install(Koin) {
    modules(module {
        single { UserRepository() }
        single { UserService(get()) }
    })
}

Spring Boot: Built-in DI with constructor injection.

@Service
class UserService(
    private val userRepository: UserRepository
)

Spring Boot wins here on convenience. But Ktor gives you more control.

Database Access

Ktor: Use any library. Common choices are Exposed, JOOQ, or raw JDBC.

// Exposed DSL
object Users : Table() {
    val id = integer("id").autoIncrement()
    val name = varchar("name", 50)
    override val primaryKey = PrimaryKey(id)
}

transaction {
    Users.insert { it[name] = "Alex" }
}

Spring Boot: Built-in Spring Data JPA.

@Entity
data class User(
    @Id @GeneratedValue
    val id: Long = 0,
    val name: String
)

interface UserRepository : JpaRepository<User, Long>

Spring Data JPA generates repository implementations automatically. Exposed requires you to write queries yourself. The trade-off: Spring Boot is faster to start, but Exposed gives you more control and type-safety.

Testing

Ktor:

@Test
fun `test hello endpoint`() = testApplication {
    application { module() }

    val response = client.get("/hello")
    assertEquals(HttpStatusCode.OK, response.status)
    assertEquals("Hello, World!", response.bodyAsText())
}

Spring Boot:

@SpringBootTest
@AutoConfigureMockMvc
class HelloControllerTest {
    @Autowired
    lateinit var mockMvc: MockMvc

    @Test
    fun `test hello endpoint`() {
        mockMvc.perform(get("/hello"))
            .andExpect(status().isOk)
            .andExpect(content().string("Hello, World!"))
    }
}

Ktor tests are simpler and faster — no Spring context to load. Spring Boot tests need to start the entire application context, which takes seconds.

Feature Comparison

FeatureKtorSpring Boot
JSON serializationPlugin (kotlinx.serialization, Gson, Jackson)Built-in (Jackson, Gson)
AuthenticationPlugin (JWT, OAuth, Basic, Session)Spring Security (very powerful but complex)
DatabaseExternal (Exposed, JOOQ)Built-in (Spring Data JPA, JDBC)
Dependency injectionExternal (Koin, Kodein)Built-in (Spring DI)
WebSocketsPluginBuilt-in
ValidationManual or externalBuilt-in (Bean Validation)
CachingPluginBuilt-in (Spring Cache)
MonitoringPlugin (Micrometer)Built-in (Actuator)
OpenAPI/SwaggerPluginBuilt-in (SpringDoc)
TemplatingPlugin (FreeMarker, Thymeleaf)Built-in (Thymeleaf, etc.)

Spring Boot has more features built-in. Ktor requires you to add plugins, but you only include what you actually use.

Performance

This matters for production. Let’s compare:

MetricKtor + NettySpring Boot + TomcatSpring Boot + WebFlux
Startup time~1-2 seconds~5-15 seconds~3-8 seconds
Memory usage~50-100 MB~200-400 MB~150-300 MB
Requests/secondHighMediumHigh
JAR size~20 MB~50-80 MB~40-60 MB

Ktor starts faster, uses less memory, and produces smaller artifacts. This matters for:

  • Microservices — Faster scaling, lower cloud costs
  • Serverless — Cold start time is critical
  • Container deployments — Smaller images, faster deploys

Spring Boot 3.x with virtual threads closes the performance gap, but Ktor still has the edge for lightweight services.

Learning Curve

Ktor

  • Easy if you know Kotlin (coroutines, DSL, extension functions)
  • Small API surface — fewer concepts to learn
  • Less magic — you see exactly what happens
  • Challenge: Fewer tutorials and Stack Overflow answers

Spring Boot

  • Easy to start — many templates and generators
  • Steep later — many abstractions to understand (DI, AOP, proxies, context)
  • Magic — annotations do things behind the scenes
  • Advantage: Huge ecosystem, millions of tutorials

If you already know Spring Boot from Java, stick with it. If you are starting fresh with Kotlin, Ktor is easier to learn.

Ecosystem

Ktor Ecosystem

  • kotlinx.serialization — JSON, Protobuf, CBOR
  • Exposed — SQL database access
  • Koin — Dependency injection
  • Ktor Client — HTTP client (also works in KMP)
  • Micrometer — Metrics and monitoring

Spring Boot Ecosystem

  • Spring Data — Database access (JPA, JDBC, MongoDB, Redis)
  • Spring Security — Authentication and authorization
  • Spring Cloud — Microservice patterns (discovery, config, gateway)
  • Spring Batch — Batch processing
  • Spring Integration — Message-driven integration

Spring Boot has a much larger ecosystem. If you need something specific, there is probably a Spring module for it.

When to Use Ktor

Choose Ktor when:

  • You are a Kotlin developer who wants a Kotlin-native experience
  • You are building microservices or small to medium APIs
  • You want fast startup and low memory usage
  • You prefer explicit configuration over magic
  • You are building a backend for a mobile app (especially with KMP)
  • You want to learn backend development with Kotlin

When to Use Spring Boot

Choose Spring Boot when:

  • Your team already knows Spring from Java
  • You need the enterprise ecosystem (Spring Security, Spring Cloud, etc.)
  • You are building a large monolith with many features
  • You need extensive third-party integrations
  • You want the largest community and most documentation

Can You Use Both?

Yes. Many companies use both:

  • Ktor for lightweight microservices and API gateways
  • Spring Boot for complex business logic and legacy integration

They can run side by side in the same infrastructure.

Migration Path

Moving from Spring Boot to Ktor (or vice versa) is not trivial. But the Kotlin code is mostly the same. Business logic, data classes, and utilities transfer directly. Only the framework-specific code (controllers, configuration, DI setup) needs to change.

If you want to try Ktor without migrating, start with a new microservice. Keep your existing Spring Boot apps running.

Our Choice: Ktor

For this tutorial series, we chose Ktor because:

  1. Kotlin-native — No Java baggage
  2. Lightweight — Perfect for learning
  3. Coroutines — Modern concurrency
  4. Explicit — You understand everything that happens
  5. Pairs with KMP — The Ktor Client is used in KMP apps

Spring Boot is an excellent framework. But for Kotlin developers building APIs and mobile backends, Ktor is the natural choice.

Source Code

You can find the source code for this tutorial on GitHub:

github.com/kemalcodes/ktor-tutorial — Branch: tutorial-02-vs-spring-boot

What’s Next?

In the next tutorial, we will set up a proper Ktor project with the right structure, configuration files, and plugins. We will move from a simple example to a real project setup.

Ktor Tutorial #3: Project Setup — Your First Ktor Application