A Guide to Spring Boot ConfigurationProperties for Kotlin Data Class
Tags: Coding, Programming, Software Engineering, Software Development, Kotlin

Additional Note on Writing the DynamoConfigProperties dataclass

When you look at how DynamoConfigProperties was written, you might be a bit hesitant or think that it’s a bit messy (or ugly) as we declared the fields as var instead of val , despite the fact that the values will never change (or should not change, I should say).

Also, we need to provide a default value of either an empty string "" (or any String , really) or null (for this, the type of the fields in DynamoConfigProperties needs to be changed to String? ).

If you declare them as val or do not provide a default value and run the application, you will see the following error.

2020-01-16 07:21:57.883 WARN 97395 --- [ main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerHandler' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerHandler.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerRepo' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerRepo.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dynamoClientProperties' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/DynamoClientProperties.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dynamoConfigProperties' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/DynamoConfigProperties.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

2020-01-16 07:21:57.960 INFO 97395 --- [ main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.

2020-01-16 07:21:58.085 ERROR 97395 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : ***************************

APPLICATION FAILED TO START

*************************** Description: Parameter 0 of constructor in io.codebrews.dynamodemo.DynamoConfigProperties required a bean of type 'java.lang.String' that could not be found. Action: Consider defining a bean of type 'java.lang.String' in your configuration. Process finished with exit code 1

You can see that Spring complains about the constructor of our DynamoConfigProperties . 😫

I guess we just have to deal with how “ugly” our configuration dataclass was written, don’t we? 🤷‍♂

NOPE, we don’t. Here comes Spring Boot 2.2.x.RELEASE to the rescue. 😊

Using ConstructorBinding annotation for the configuration dataclass

If your project uses Spring Boot framework version 2.2.0.RELEASE or above, you can change the way you write DynamoConfigProperties as follows.

DynamoConfigProperties.kt with ConstructorBinding

Now, go ahead and run the main function of the application again. You should see the below exception getting thrown at ya. 😦

2020-01-16 13:29:03.367 WARN 87367 --- [ main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerHandler' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerHandler.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerRepo' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerRepo.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dynamoClientProperties' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/DynamoClientProperties.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.codebrews.dynamodemo.DynamoConfigProperties' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

2020-01-16 13:29:03.422 INFO 87367 --- [ main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.

2020-01-16 13:29:03.498 ERROR 87367 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : ***************************

APPLICATION FAILED TO START

*************************** Description: Parameter 0 of constructor in io.codebrews.dynamodemo.DynamoClientProperties required a bean of type 'io.codebrews.dynamodemo.DynamoConfigProperties' that could not be found. Action: Consider defining a bean of type 'io.codebrews.dynamodemo.DynamoConfigProperties' in your configuration. Process finished with exit code 1

Unfortunately, Spring will complain to you that it cannot find the DynamoConfigProperties bean, which is required by our application to run. The reason for this is because Spring does not scan for classes annotated by @ConfigurationProperties .

Our initial DynamoConfigProperties works fine because we annotate it with @Component , which is scanned by Spring when it is building the Application Context.

I know what you’re thinking… You are going to add @Component annotation to the updated DynamoConfigProperties , ain’t you? But unfortunately, this won’t work either and it will throw you this error when you start the application.



2020-01-16 13:45:06.031 INFO 92292 --- [ main] ConditionEvaluationReportLoggingListener : 2020-01-16 13:45:06.021 WARN 92292 --- [ main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dynamoConfigProperties': @EnableConfigurationP roperties or @ConfigurationPropert iesScan must be used to add @ConstructorBinding type io.codebrews.dynamodemo.DynamoConfigProperties2020-01-16 13:45:06.031 INFO 92292 --- [ main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.

2020-01-16 13:45:06.039 ERROR 92292 --- [ main] o.s.boot.SpringApplication : Application run failed

at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.validate(ConfigurationPropertiesBeanDefinitionValidator.java:66) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.postProcessBeanFactory(ConfigurationPropertiesBeanDefinitionValidator.java:45) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:286) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]

at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]

at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]

at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]

at io.codebrews.dynamodemo.DynamodemoApplicationKt.main(DynamodemoApplication.kt:15) [classes/:na] org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dynamoConfigProperties': @EnableConfigurationP roperties or @ConfigurationPropert iesScan must be used to add @ConstructorBinding type io.codebrews.dynamodemo.DynamoConfigPropertiesat org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.validate(ConfigurationPropertiesBeanDefinitionValidator.java:66) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.postProcessBeanFactory(ConfigurationPropertiesBeanDefinitionValidator.java:45) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:286) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]at io.codebrews.dynamodemo.DynamodemoApplicationKt.main(DynamodemoApplication.kt:15) [classes/:na] Process finished with exit code 1

So, what do we do now? We improved the way our code is written, however, our application crashes! Should we go back to the old ways?

Absolutely not! 🙅‍♂

Here’s how to fix it. (It is very simple, actually).

Applying ConfigurationPropertiesScan or EnableConfigurationProperties to our main application class

By the virtue of another Spring Boot’s annotation — @ConfigurationPropertiesScan . The error message from the case where we tried to annotate the updated DynamoConfigProperties with @Component actually tells us to use either @ConfigurationPropertiesScan or @EnableConfigurationProperties .

We’re going to go ahead with @ConfigurationPropertiesScan in this tutorial. So, let’s add that annotation to our DynamodemoApplication class like this.

DynamodemoApplication.kt with ConfigurationPropertiesScan

Alternatively, in case you want to use @EnableConfigurationProperties instead, this is how you do it. It takes a little extra step to make it work, i.e. you need to specify explicitly which configuration class you want to enable. In our case, it is DynamoConfigProperties .

DynamodemoApplication.kt with EnableConfigurationProperties

If you have another configuration class/es you want to enable, you need to specify all of them by separating them with a comma, e.g. @EnableConfigurationProperties(DynamoConfigProperties::class, KafkaConfigProperties::class) .

After applying either of the above, go ahead and run the main function again. No more tricks from me this time. 😜

DynamodemoApplication.kt running successfully

Voila! Everything works now.

Well done, guys! 👍 👍

Photo by Tory Morrison on Unsplash

References

The application Github — here

Tutorial on how the application was originally built — here