javaspringspring-bootkotlinconfigurationproperties

In a nested java Spring configuration property class, can a value from the parent be used to init the child?


I have a SpringBoot 2, Kotlin/Java web service that uses @ConfigurationProperties to load up a nested set of classes/properties for its use at runtime. Everything is working properly except for one thing: I cannot figure out how to pass a value (code) from the parent class (TenantProperties) to its nested child class (DatabaseProperties). In the following snippet, which is extracted from another parent with the proper annotations, I am trying to get the code in TenantProperties to be passed to the DatabaseProperties when it is constructed. No matter what I've tried, the parent always passes null to the child. I'd appreciate any suggestions.

class TenantProperties(
    var code: String? = null,
    var id: String? = null,
    var name: String? = null,
    var db: DatabaseProperties = DatabaseProperties(code),
    var blob: BlobProperties = BlobProperties(code)
) {
    class DatabaseProperties(
        var code: String?,
        var uriTemplate: String? = null,
        var username: String? = null,
        var password: String? = null
    ) {
        fun connectionString() = "$uriTemplate".format(username, password, code)
    }
}

Solution

  • In your example you initialise the classes along with the parent object. Spring configuration parameters are fed in at a later point. So your nested classes are always initialised with the default value, so a configured code will never be passed down.

    In order to get your code to work, I suggest to make the classes inner classes (in order to allow them to access the outer one) and have the post-built property initialised lazily:

    @Configuration
    @ConfigurationProperties(value =  "my")
    class TenantProperties {
    
        lateinit var code: String
        lateinit var id: String
        lateinit var name: String
    
        val db = DatabaseProperties()
    
        inner class DatabaseProperties {
            lateinit var uriTemplate: String
            lateinit var username: String
            lateinit var password: String
            val connectionString by lazy { "$uriTemplate".format(username, password, code) }
        }
    }
    

    Making all variables lateinit you gain some type safety. In order to make it even more robust, you can add some validators:

    @Configuration
    @ConfigurationProperties(value =  "my", ignoreUnknownFields = false, ignoreInvalidFields = false)
    class TenantProperties {
    
        @NotNull lateinit var code: String
        @NotNull lateinit var id: String
        @NotNull lateinit var name: String
    
        val db = DatabaseProperties()
    
        inner class DatabaseProperties {
            @NotNull lateinit var uriTemplate: String
            @NotNull lateinit var username: String
            @NotNull lateinit var password: String
            val connectionString by lazy { "$uriTemplate".format(username, password, code) }
        }
    }
    

    now, your application.properties can look like this:

    my.code=aCode
    my.id=anId
    my.name=aName
    my.db.uri-template=http://localhost
    my.db.username=admin
    my.db.password=pw