'Ktor with Gradle run configuration "Could not resolve substitution to a value" from environment variables

I have set up a server in Ktor with a Postgres database in Docker, but figured it would be useful to be able to develop the server locally without rebuilding the docker container each time.

In application.conf I have

// ...

db {
    jdbcUrl = ${DATABASE_URL}
    dbDriver = "org.postgresql.Driver"
    dbDriver = ${?DATABASE_DRIVER}
    dbUser = ${DATABASE_USER}
    dbPassword = ${DATABASE_PASSWORD}
}

and in my DatabaseFactory I have

object DatabaseFactory {

    private val appConfig = HoconApplicationConfig(ConfigFactory.load())
    private val dbUrl = appConfig.property("db.jdbcUrl").getString()
    private val dbDriver = appConfig.property("db.dbDriver").getString()
    private val dbUser = appConfig.property("db.dbUser").getString()
    private val dbPassword = appConfig.property("db.dbPassword").getString()

    fun init() {
        Database.connect(hikari())

        transaction {
            val flyway = Flyway.configure().dataSource(dbUrl, dbUser, dbPassword).load()
            flyway.migrate()
        }
    }

    private fun hikari(): HikariDataSource {
        val config = HikariConfig()
        config.driverClassName = dbDriver
        config.jdbcUrl = dbUrl
        config.username = dbUser
        config.password = dbPassword
        config.maximumPoolSize = 3
        config.isAutoCommit = false
        config.transactionIsolation = "TRANSACTION_REPEATABLE_READ"
        config.validate()
        return HikariDataSource(config)
    }

    suspend fun <T> dbQuery(block: () -> T): T =
        withContext(Dispatchers.IO) {
            transaction { block() }
        }

}

I have edited the Gradle run configuration with the following environment config:

DATABASE_URL=jdbc:h2:mem:default;DATABASE_DRIVER=org.h2.Driver;DATABASE_USER=test;DATABASE_PASSWORD=password

When I run the task I get this error: Could not resolve substitution to a value: ${DATABASE_URL}, but if I set a breakpoint on the first line (private val appConfig) and evaluate System.getenv("DATABASE_URL") it is resolved to the correct value.

My questions are:

  1. Why does this not work?
  2. What is the best (or: a good) setup for developing the server without packing it in a container? Preferably without running the database in another container.


Solution 1:[1]

You need to specify:

appConfig.property("ktor.db.jdbcUrl").getString()

Solution 2:[2]

I found that setting environment variables for the task in gradle.config.kts works:

tasks {
    "run"(JavaExec::class) {
        environment("DATABASE_URL", "jdbc:postgresql://localhost:5432/test")
        environment("DATABASE_USER", "test")
        environment("DATABASE_PASSWORD", "password")
    }
}

(source: Setting environment variables in build.gradle.kts)

As for why my initial approach only works in debug mode I have no idea. As for question #2 I have a suspicion that H2 and Postgres could have some syntactic differences that will cause trouble. Running the database container in the background works fine for now.

Solution 3:[3]

As answered by @leonardo-freitas we need to specify ktor. first before accessing application.conf properties.

environment.config.property("ktor.db.jdbcUrl").getString()

This is missing on the official doc as well https://ktor.io/docs/jwt.html#validate-payload

Solution 4:[4]

I experienced the same problem. I had to manually restart the IntelliJ (NOT via File). Simply closed the IDE, and then turned it on again. Also, check that your environment variable is permanent.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chris Catignani
Solution 2 Jørgen
Solution 3 Chetan Gaikwad
Solution 4