In Ktor, what is the difference between starting a server with embeddedServer and with EngineMain?
answer
- Two ways to start: code or file
- One reads a config file at boot
- HOCON keys under ktor.deployment / ktor.application
- Module referenced by fully-qualified name
basics
~10 sembeddedServer starts Ktor from Kotlin code, with the engine, host and port passed as arguments. EngineMain is a prebuilt main function that boots the same server from an application.conf or application.yaml file instead.
solid answer
~40 sBoth produce the same running Ktor server; they differ in where the configuration lives. `embeddedServer(Netty, port = 8080) { module() }.start(wait = true)` is *code-based* configuration: you pick the engine factory, port and host as Kotlin arguments and call your module directly. `EngineMain` is a `main` function shipped by each engine artifact — you set the application's main class to `io.ktor.server.netty.EngineMain` and it reads a config file (`application.conf` in HOCON, or `application.yaml`), taking `ktor.deployment.port` and the `ktor.application.modules` list from there. Code-based setup is the natural fit for tests and small tools, because it can be started and stopped inline on any port. File-based setup is the deployment default: ops can change the port or swap modules without recompiling, and the same jar is reconfigurable per environment.
code
kotlin · 11 linesfun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
module()
}.start(wait = true)
}
fun Application.module() {
routing {
get("/health") { call.respondText("OK") }
}
}go deeper
Recall both entry points by name and be able to write the embeddedServer one-liner from memory, including the engine argument and the port. Know that application.conf is HOCON.
Explain that the module list is resolved reflectively by fully-qualified JVM name at startup, and why that means a typo fails at boot instead of at compile time.
Argue the deployment consequence: file-based config lets one artifact be repointed per environment, so pick EngineMain for services and code-based startup for tests and tools.
Own the convention across services — one main class, one config layout, modules split so that environments can enable subsets — so that operability does not vary team by team.
## The two entry points A Ktor server is an engine plus one or more *modules*. Ktor gives you two ways to assemble them, and interviewers ask about the difference because it decides how the service is configured in production. ### embeddedServer — configuration in code ``` fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { module() }.start(wait = true) } ``` `embeddedServer` is a function that takes an *engine factory* (`Netty`, `CIO`, `Jetty`, `Tomcat`) as its first argument, some connector settings, and a lambda that runs with `Application` as its receiver. It returns a server object you can `start()` and `stop()`. `wait = true` blocks the calling thread until the server stops; `wait = false` returns immediately, which is what tests want so they can issue requests and then shut the server down. Everything is visible in Kotlin: the port is a literal or a variable, the module is a direct function call, and nothing is resolved by name at runtime. That makes it refactor-safe and IDE-navigable. ### EngineMain — configuration in a file Each server engine artifact ships an object called `EngineMain` with a `main(args)` function. You point your build at it: ``` application { mainClass.set("io.ktor.server.netty.EngineMain") } ``` and put the settings in `src/main/resources/application.conf`: ``` ktor { deployment { port = 8080 } application { modules = [ com.example.ApplicationKt.module ] } } ``` The format is HOCON (Human-Optimized Config Object Notation), the Typesafe Config format. Ktor also supports `application.yaml` when the YAML config artifact is on the classpath. `EngineMain` loads that file, builds the engine, and then resolves every entry in `ktor.application.modules` **by fully-qualified name** and invokes it against the `Application`. That name is the part people get wrong. A top-level function `fun Application.module()` declared in `Application.kt` compiles to a static method on the class `ApplicationKt`, so the reference is `com.example.ApplicationKt.module`. Misspell it and the server fails at startup with a class-not-found style error rather than a compile error — the cost of late binding. ## Why the choice matters **Reconfiguration without a rebuild.** With `EngineMain` the port, the connector, the log level and even which modules are enabled are data. A container image can be reused across environments and driven by config. With `embeddedServer` the same change is a code change. **Environment overrides.** File-based setup composes naturally with HOCON substitution such as `port = ${?PORT}`, which takes the value from an environment variable if it is set and leaves the previous value otherwise — exactly the shape a PaaS or Kubernetes deployment wants. **Tests.** Test code overwhelmingly uses code-based startup, directly or through Ktor's test host, because it needs an ephemeral port and an in-process lifecycle. A common production layout therefore ships `EngineMain` for the real deployment while tests call the same `Application.module()` function through `embeddedServer` or the testing DSL. Because a module is just an extension function on `Application`, both paths reuse it unchanged. **Hybrid.** The two are not exclusive. You can start `embeddedServer` and still read `application.conf`, since the config is available through the application environment; conversely `EngineMain` accepts command-line arguments that override file values. ## What a good answer includes Say that both end up at the same `Application` object, name HOCON/`application.conf` as what `EngineMain` reads, mention `ktor.deployment.port` and `ktor.application.modules`, and give the practical rule: file-based for deployment flexibility, code-based for tests and single-purpose tools. Note the tradeoff honestly — the config file buys you runtime flexibility and costs you compile-time checking of module references.
- Why does a module reference in application.conf look like com.example.ApplicationKt.module rather than com.example.module?A Kotlin top-level function is compiled into a synthetic class named after its file — `Application.kt` becomes `ApplicationKt` — and the module is a static method on it. Ktor resolves the string reflectively, so it needs the JVM name, not the Kotlin source name. Renaming the file silently breaks startup.
- How would you start a Ktor server on a random free port inside a test?Use code-based startup with port 0 and `wait = false`, then read the actually bound port back from the engine's resolved connectors before issuing requests. Ktor's own testing support does the equivalent for you, which is why tests almost never go through EngineMain.
- Can one application.conf list more than one module?Yes. `ktor.application.modules` is a list, and Ktor invokes each entry against the same `Application` in order. That is how teams split wiring into, say, a routing module, an observability module and a persistence module, and how a config file can enable or disable one of them per environment.
saying these in an interview costs you the question
- Claims EngineMain is faster or uses a different engine
- Thinks embeddedServer cannot read application.conf at all
- States the modules entry is a Kotlin function reference checked at compile time
- Says wait = true is required for the server to serve requests
- Confuses application.conf with a Spring application.properties equivalent