Skip to main content
Version: v0.13

Configuration

The IDK provides a powerful configuration system that lets you read configuration values from multiple sources without worrying about where they come from. Your application code simply calls configService.getProperty("my.key") and the system handles resolution from environment variables, property files, cloud providers, and databases.

Quick Start

The most common pattern is reading configuration values through the ConfigService:

// Get ConfigService from your DI component
val configService: ConfigService = appComponent.configService

// Read a string property
val apiUrl = configService.getProperty("api.base.url", String::class)

// Read with a default value
val timeout = configService.getProperty("http.timeout.ms", Int::class, 30000)

// Read a required property (throws if missing)
val apiKey = configService.getRequiredProperty("api.key", String::class)

// Check if a property exists
if (configService.containsProperty("feature.enabled")) {
// ...
}

ConfigService API

The ConfigService interface provides type-safe access to configuration:

MethodDescription
getProperty(key, type)Get a property value, returns null if not found
getProperty(key, type, default)Get a property with a default fallback
getRequiredProperty(key, type)Get a property, throws if not found
containsProperty(key)Check if a property exists
getAllProperties()Get all properties as a map
getSubProperties(prefix)Get all properties under a prefix
getActiveProfile()Get the current configuration profile
getAppName()Get the application name

Supported Types

The configuration system supports automatic type conversion:

// String
val name = configService.getProperty("app.name", String::class)

// Numbers
val port = configService.getProperty("server.port", Int::class)
val timeout = configService.getProperty("timeout.ms", Long::class)
val ratio = configService.getProperty("cache.ratio", Double::class)

// Boolean
val enabled = configService.getProperty("feature.enabled", Boolean::class)

Configuration Sources

Configuration values are resolved from multiple sources in priority order:

PrioritySourceDescription
1 (Highest)Environment VariablesAPI_BASE_URL maps to api.base.url
2System PropertiesJVM -D properties
3Cloud ProvidersAzure App Config, REST API servers
4DatabasePersisted settings (VDX only)
5Property Filesapplication-{profile}.properties
6 (Lowest)Programmatic DefaultsCode-defined defaults

Higher priority sources override lower priority ones. This means environment variables always win, allowing production overrides without code changes.

Key Normalization

Keys are normalized for consistent lookup across sources:

Property KeyEnvironment VariableNormalized Form
api.base.urlAPI_BASE_URLapi.base.url
http.timeout-msHTTP_TIMEOUT_MShttp.timeout.ms
OAuth2.ClientIdOAUTH2_CLIENTIDoauth2.clientid

You can use any format when reading properties - they'll all resolve to the same value:

// All these read the same configuration value
configService.getProperty("api.base.url", String::class)
configService.getProperty("api.baseUrl", String::class)
configService.getProperty("api.base-url", String::class)

Property Files

Create application-{profile}.properties files in your classpath:

application-production.properties
# API Configuration
api.base.url=https://api.production.example.com
api.timeout.ms=30000

# Feature Flags
feature.new-dashboard.enabled=true
feature.beta-features.enabled=false

# Logging
logging.level=INFO
application-development.properties
# API Configuration
api.base.url=http://localhost:8080
api.timeout.ms=60000

# Feature Flags
feature.new-dashboard.enabled=true
feature.beta-features.enabled=true

# Logging
logging.level=DEBUG

The profile is set during application initialization:

val appComponent = IdkAppComponent.init(
appId = "my-app",
profile = "production", // Loads application-production.properties
version = "1.0.0"
)

Environment Variables

Environment variables provide the highest priority configuration source. They're ideal for:

  • Production deployments
  • Secrets and credentials
  • Container/Kubernetes configurations
  • CI/CD pipeline overrides
# Set configuration via environment variables
export API_BASE_URL=https://api.example.com
export API_KEY=secret-key-value
export LOG_LEVEL=DEBUG
export FEATURE_NEW_DASHBOARD_ENABLED=true

Conversion Rules

Property FormatEnvironment Variable
api.base.urlAPI_BASE_URL
oauth2.client-idOAUTH2_CLIENT_ID
kms.providers.aws.regionKMS_PROVIDERS_AWS_REGION

Writing Configuration

You can write configuration programmatically using property sources:

// Add a property source with configuration values
val myConfig = MapPropertySource(
name = "my-custom-config",
source = mapOf(
"api.base.url" to "https://api.example.com",
"http.timeout.ms" to 30000,
"feature.enabled" to true
)
)

configService.addPropertySource(myConfig)

For persistent configuration storage, see Database Persistence (EDK/VDX only).

Scoped Configuration

The configuration system supports hierarchical scopes for multi-tenant applications:

ScopeDescriptionUse Cases
AppApplication-wide settingsAPI endpoints, default timeouts, feature flags
TenantOrganization-specific settingsAPI keys, branding, org feature flags
PrincipalUser-specific settingsPreferences, individual credentials

Each scope inherits from its parent, so tenant settings can override app settings, and principal settings can override tenant settings.

// App-level configuration (shared by all tenants)
val appConfigService: AppConfigService = appComponent.appConfigService
val apiUrl = appConfigService.getProperty("api.base.url", String::class)

// Tenant-level configuration (inherits from app)
val tenantConfigService: TenantConfigService = tenantComponent.tenantConfigService
val tenantApiKey = tenantConfigService.getProperty("api.subscription.key", String::class)

// Principal-level configuration (inherits from tenant)
val principalConfigService: PrincipalConfigService = principalComponent.principalConfigService
val userTimeout = principalConfigService.getProperty("http.timeout.ms", Int::class)

For more details on multi-tenant configuration, see Multi-Tenancy.

Common Configuration Patterns

Feature Flags

// Check if a feature is enabled
val isNewDashboardEnabled = configService.getProperty(
"feature.new-dashboard.enabled",
Boolean::class
) ?: false

if (isNewDashboardEnabled) {
showNewDashboard()
} else {
showLegacyDashboard()
}

API Client Configuration

// Configure an API client from properties
val apiConfig = ApiClientConfig(
baseUrl = configService.getRequiredProperty("api.base.url", String::class),
timeoutMs = configService.getProperty("api.timeout.ms", Long::class, 30000L),
retryCount = configService.getProperty("api.retry.count", Int::class, 3)
)

Getting All Properties with a Prefix

// Get all properties under "kms.providers"
val kmsProviderProps = configService.getSubProperties(
prefixes = setOf("kms.providers"),
stripPrefix = true // "kms.providers.aws.region" becomes "aws.region"
)

kmsProviderProps.forEach { (key, value) ->
println("$key = $value")
}

Best Practices

Use environment variables for secrets. Never commit API keys, passwords, or other sensitive values to source control. Use environment variables in production.

Define sensible defaults. Property files should contain reasonable defaults for development. Production values come from environment variables.

Use descriptive key names. Follow dot-notation conventions: component.feature.setting (e.g., http.client.timeout.ms).

Validate at startup. Check that required configuration is present before serving requests:

fun validateConfig(configService: ConfigService) {
val requiredKeys = listOf("api.base.url", "api.key", "database.url")

val missing = requiredKeys.filter { !configService.containsProperty(it) }
if (missing.isNotEmpty()) {
throw IllegalStateException("Missing required configuration: $missing")
}
}

Use typed configuration classes for complex configuration:

data class DatabaseConfig(
val url: String,
val username: String,
val password: String,
val poolSize: Int = 10
) {
companion object {
fun fromConfigService(config: ConfigService) = DatabaseConfig(
url = config.getRequiredProperty("database.url", String::class),
username = config.getRequiredProperty("database.username", String::class),
password = config.getRequiredProperty("database.password", String::class),
poolSize = config.getProperty("database.pool.size", Int::class, 10)
)
}
}

Next Steps