Ktor Server Core library providing foundational infrastructure for building asynchronous web applications and REST APIs with Kotlin
—
Ktor's configuration system provides flexible application configuration management through multiple sources including files, environment variables, and programmatic configuration. The system supports hierarchical configuration with type-safe access and validation.
interface ApplicationConfig {
fun property(path: String): ApplicationConfigValue
fun propertyOrNull(path: String): ApplicationConfigValue?
fun config(path: String): ApplicationConfig
fun configList(path: String): List<ApplicationConfig>
fun keys(): Set<String>
fun toMap(): Map<String, Any?>
}interface ApplicationConfigValue {
val type: Type
fun getString(): String
fun getList(): List<String>
fun getInt(): Int
fun getLong(): Long
fun getFloat(): Float
fun getDouble(): Double
fun getBoolean(): Boolean
}// Access configuration properties
fun Application.loadConfig() {
val config = environment.config
// String properties
val appName = config.property("app.name").getString()
val version = config.propertyOrNull("app.version")?.getString() ?: "1.0.0"
// Numeric properties
val port = config.property("server.port").getInt()
val timeout = config.property("server.timeout").getLong()
val rate = config.property("app.rate").getDouble()
// Boolean properties
val debugMode = config.property("app.debug").getBoolean()
val enableLogging = config.propertyOrNull("logging.enabled")?.getBoolean() ?: true
// List properties
val allowedHosts = config.property("server.allowedHosts").getList()
val modules = config.propertyOrNull("app.modules")?.getList() ?: emptyList()
}// Configuration backed by Map
class MapApplicationConfig(vararg values: Pair<String, String>) : ApplicationConfig {
constructor(map: Map<String, String>)
// Add or update configuration values
fun put(key: String, value: String)
fun putAll(values: Map<String, String>)
}
// Create programmatic configuration
val config = MapApplicationConfig(
"app.name" to "MyKtorApp",
"server.port" to "8080",
"server.host" to "0.0.0.0",
"database.url" to "jdbc:h2:mem:test",
"database.driver" to "org.h2.Driver",
"logging.level" to "INFO"
)
// Use with application environment
val environment = applicationEnvironment {
this.config = config
}// Configuration that merges multiple configs with precedence
class MergedApplicationConfig(
vararg configs: ApplicationConfig
) : ApplicationConfig {
// Later configs override earlier ones
}
// Example: Environment variables override file config
val fileConfig = HoconApplicationConfig(ConfigFactory.load())
val envConfig = MapApplicationConfig(System.getenv().mapKeys { "env.${it.key}" })
val merged = MergedApplicationConfig(fileConfig, envConfig)// Configuration using Typesafe Config (HOCON format)
class HoconApplicationConfig(
private val config: Config
) : ApplicationConfig {
constructor(configPath: String) : this(ConfigFactory.load(configPath))
// Access nested configuration
override fun config(path: String): ApplicationConfig {
return HoconApplicationConfig(config.getConfig(path))
}
}
// Load HOCON configuration
val config = HoconApplicationConfig(ConfigFactory.load())
// application.conf example:
/*
app {
name = "MyKtorApp"
version = "1.0.0"
debug = false
server {
port = 8080
host = "0.0.0.0"
ssl {
enabled = false
keyStore = "keystore.jks"
keyAlias = "mykey"
}
}
database {
url = "jdbc:postgresql://localhost:5432/mydb"
driver = "org.postgresql.Driver"
user = "dbuser"
password = "dbpass"
pool {
maxSize = 20
minIdle = 5
maxLifetime = 1800000
}
}
logging {
level = "INFO"
appenders = ["console", "file"]
file {
path = "logs/app.log"
maxSize = "10MB"
maxHistory = 30
}
}
}
*/fun Application.configureFromHocon() {
val config = environment.config
// Access nested configurations
val serverConfig = config.config("server")
val port = serverConfig.property("port").getInt()
val host = serverConfig.property("host").getString()
val sslConfig = serverConfig.config("ssl")
val sslEnabled = sslConfig.property("enabled").getBoolean()
if (sslEnabled) {
val keyStore = sslConfig.property("keyStore").getString()
val keyAlias = sslConfig.property("keyAlias").getString()
// Configure SSL
}
// Access configuration lists
val dbConfigs = config.configList("databases")
dbConfigs.forEach { dbConfig ->
val url = dbConfig.property("url").getString()
val driver = dbConfig.property("driver").getString()
// Configure database connection
}
}// Create configuration from environment variables
fun createEnvironmentConfig(): ApplicationConfig {
val envVars = System.getenv()
val configMap = mutableMapOf<String, String>()
// Map environment variables to config paths
envVars.forEach { (key, value) ->
when {
key.startsWith("KTOR_") -> {
val configKey = key.removePrefix("KTOR_")
.lowercase()
.replace('_', '.')
configMap[configKey] = value
}
}
}
return MapApplicationConfig(configMap)
}
// Example environment variables:
// KTOR_SERVER_PORT=8080
// KTOR_SERVER_HOST=0.0.0.0
// KTOR_DATABASE_URL=jdbc:postgresql://localhost/mydb
// KTOR_LOGGING_LEVEL=DEBUG
// Usage
val envConfig = createEnvironmentConfig()
val fileConfig = HoconApplicationConfig(ConfigFactory.load())
val config = MergedApplicationConfig(fileConfig, envConfig) // Env overrides file// Create configuration from system properties
fun createSystemPropsConfig(): ApplicationConfig {
val sysProps = System.getProperties()
val configMap = mutableMapOf<String, String>()
sysProps.forEach { key, value ->
if (key.toString().startsWith("ktor.")) {
configMap[key.toString()] = value.toString()
}
}
return MapApplicationConfig(configMap)
}
// Usage with JVM arguments:
// java -Dktor.server.port=8080 -Dktor.debug=true MyAppobject ConfigLoaders {
fun file(path: String): ApplicationConfig {
return HoconApplicationConfig(ConfigFactory.parseFile(File(path)))
}
fun classpath(resource: String): ApplicationConfig {
return HoconApplicationConfig(ConfigFactory.parseResources(resource))
}
fun properties(path: String): ApplicationConfig {
val props = Properties()
File(path).inputStream().use { props.load(it) }
return MapApplicationConfig(props.toMap() as Map<String, String>)
}
fun environment(): ApplicationConfig {
return createEnvironmentConfig()
}
fun systemProperties(): ApplicationConfig {
return createSystemPropsConfig()
}
}
// Load configuration from multiple sources
val config = MergedApplicationConfig(
ConfigLoaders.classpath("application.conf"), // Default config
ConfigLoaders.file("config/production.conf"), // Environment-specific
ConfigLoaders.environment(), // Environment variables
ConfigLoaders.systemProperties() // JVM system properties
)// Decoder for map-based configurations
class MapConfigDecoder {
fun decode(map: Map<String, Any?>): ApplicationConfig {
return MapApplicationConfig().apply {
map.forEach { (key, value) ->
when (value) {
is String -> put(key, value)
is Number -> put(key, value.toString())
is Boolean -> put(key, value.toString())
is List<*> -> {
// Handle list values
value.forEachIndexed { index, item ->
put("$key.$index", item.toString())
}
}
is Map<*, *> -> {
// Handle nested maps
@Suppress("UNCHECKED_CAST")
val nested = value as Map<String, Any?>
nested.forEach { (nestedKey, nestedValue) ->
put("$key.$nestedKey", nestedValue.toString())
}
}
}
}
}
}
}// Extension properties for common deployment settings
val ApplicationConfig.port: Int
get() = propertyOrNull("ktor.deployment.port")?.getInt() ?: 8080
val ApplicationConfig.host: String
get() = propertyOrNull("ktor.deployment.host")?.getString() ?: "0.0.0.0"
val ApplicationConfig.sslPort: Int?
get() = propertyOrNull("ktor.security.ssl.port")?.getInt()
val ApplicationConfig.developmentMode: Boolean
get() = propertyOrNull("ktor.development")?.getBoolean() ?: false
val ApplicationConfig.rootPath: String
get() = propertyOrNull("ktor.deployment.rootPath")?.getString() ?: ""
// Usage
fun Application.configureFromDeployment() {
val config = environment.config
log.info("Starting server on ${config.host}:${config.port}")
log.info("Development mode: ${config.developmentMode}")
log.info("Root path: ${config.rootPath}")
config.sslPort?.let { sslPort ->
log.info("SSL enabled on port $sslPort")
}
}// Configure watch paths for development auto-reload
fun ApplicationEnvironmentBuilder.configureWatchPaths(config: ApplicationConfig) {
config.propertyOrNull("ktor.deployment.watch")?.getList()?.forEach { path ->
watchPaths.add(path)
}
// Or from individual watch entries
config.configList("ktor.deployment.watch").forEach { watchConfig ->
val path = watchConfig.property("path").getString()
watchPaths.add(path)
}
}
// application.conf example:
/*
ktor {
deployment {
watch = [
"src/main/kotlin",
"src/main/resources"
]
# Alternative format
watch = [
{ path = "src/main/kotlin" },
{ path = "src/main/resources" }
]
}
}
*/// Define configuration data classes
data class ServerConfig(
val port: Int,
val host: String,
val ssl: SslConfig?
)
data class SslConfig(
val port: Int,
val keyStore: String,
val keyPassword: String,
val keyAlias: String
)
data class DatabaseConfig(
val url: String,
val driver: String,
val user: String,
val password: String,
val pool: PoolConfig
)
data class PoolConfig(
val maxSize: Int,
val minIdle: Int,
val maxLifetime: Long
)
// Extension functions for type-safe access
fun ApplicationConfig.getServerConfig(): ServerConfig {
val serverConfig = config("server")
return ServerConfig(
port = serverConfig.property("port").getInt(),
host = serverConfig.property("host").getString(),
ssl = serverConfig.propertyOrNull("ssl")?.let {
val sslConfig = serverConfig.config("ssl")
SslConfig(
port = sslConfig.property("port").getInt(),
keyStore = sslConfig.property("keyStore").getString(),
keyPassword = sslConfig.property("keyPassword").getString(),
keyAlias = sslConfig.property("keyAlias").getString()
)
}
)
}
fun ApplicationConfig.getDatabaseConfig(): DatabaseConfig {
val dbConfig = config("database")
return DatabaseConfig(
url = dbConfig.property("url").getString(),
driver = dbConfig.property("driver").getString(),
user = dbConfig.property("user").getString(),
password = dbConfig.property("password").getString(),
pool = dbConfig.config("pool").let { poolConfig ->
PoolConfig(
maxSize = poolConfig.property("maxSize").getInt(),
minIdle = poolConfig.property("minIdle").getInt(),
maxLifetime = poolConfig.property("maxLifetime").getLong()
)
}
)
}// Validation extension functions
fun ApplicationConfigValue.requireInt(min: Int? = null, max: Int? = null): Int {
val value = getInt()
min?.let { require(value >= it) { "Value must be >= $it, got $value" } }
max?.let { require(value <= it) { "Value must be <= $it, got $value" } }
return value
}
fun ApplicationConfigValue.requireString(pattern: Regex? = null): String {
val value = getString()
require(value.isNotBlank()) { "Value cannot be blank" }
pattern?.let { require(value.matches(it)) { "Value '$value' doesn't match pattern $it" } }
return value
}
fun ApplicationConfig.requireProperty(path: String): ApplicationConfigValue {
return propertyOrNull(path) ?: throw IllegalStateException("Required property '$path' not found")
}
// Validation example
fun Application.validateConfiguration() {
val config = environment.config
try {
// Validate required properties
val port = config.requireProperty("server.port").requireInt(min = 1, max = 65535)
val host = config.requireProperty("server.host").requireString()
val dbUrl = config.requireProperty("database.url")
.requireString(Regex("^jdbc:[a-zA-Z0-9]+://.*"))
log.info("Configuration validated successfully")
log.info("Server will start on $host:$port")
} catch (e: IllegalStateException) {
log.error("Configuration validation failed: ${e.message}")
throw e
} catch (e: IllegalArgumentException) {
log.error("Configuration validation failed: ${e.message}")
throw e
}
}// Environment-specific configuration loading
class ConfigurationManager {
fun loadConfiguration(environment: String = "development"): ApplicationConfig {
val configs = mutableListOf<ApplicationConfig>()
// Base configuration
configs.add(ConfigLoaders.classpath("application.conf"))
// Environment-specific configuration
val envConfigFile = "application-$environment.conf"
try {
configs.add(ConfigLoaders.classpath(envConfigFile))
} catch (e: Exception) {
println("Environment config $envConfigFile not found, using defaults")
}
// External configuration file (if exists)
val externalConfig = File("config/application.conf")
if (externalConfig.exists()) {
configs.add(ConfigLoaders.file(externalConfig.path))
}
// Environment variables (highest priority)
configs.add(ConfigLoaders.environment())
// System properties (highest priority)
configs.add(ConfigLoaders.systemProperties())
return MergedApplicationConfig(*configs.toTypedArray())
}
}
// Application configuration setup
fun main() {
val environment = System.getenv("APP_ENV") ?: "development"
val configManager = ConfigurationManager()
val config = configManager.loadConfiguration(environment)
val app = embeddedServer(Netty, environment = applicationEnvironment {
this.config = config
configure(config)
developmentMode = config.developmentMode
}) {
configureApplication()
}
app.start(wait = true)
}
fun Application.configureApplication() {
val config = environment.config
// Validate configuration
validateConfiguration()
// Load type-safe configuration
val serverConfig = config.getServerConfig()
val dbConfig = config.getDatabaseConfig()
// Configure application based on config
configureDatabase(dbConfig)
configureSecurity(config)
configureRouting()
log.info("Application configured successfully")
}
fun Application.configureDatabase(dbConfig: DatabaseConfig) {
// Database configuration based on config
log.info("Configuring database: ${dbConfig.url}")
}
fun Application.configureSecurity(config: ApplicationConfig) {
val securityConfig = config.config("security")
if (securityConfig.propertyOrNull("jwt.enabled")?.getBoolean() == true) {
val jwtSecret = securityConfig.property("jwt.secret").getString()
val jwtIssuer = securityConfig.property("jwt.issuer").getString()
// Configure JWT
log.info("JWT security configured")
}
if (securityConfig.propertyOrNull("cors.enabled")?.getBoolean() == true) {
val allowedHosts = securityConfig.property("cors.allowedHosts").getList()
// Configure CORS
log.info("CORS configured for hosts: $allowedHosts")
}
}
// Configuration files structure:
/*
resources/
├── application.conf # Base configuration
├── application-development.conf # Development overrides
├── application-staging.conf # Staging overrides
├── application-production.conf # Production overrides
└── logback.xml # Logging configuration
config/ # External configuration
└── application.conf # Local overrides
*/This comprehensive configuration documentation covers all aspects of Ktor's configuration system, from basic property access to advanced multi-environment configuration management with validation and type safety.
Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-server-core