A Guide to Spring Boot ConfigurationProperties for Kotlin Data Class | by billydharmawan | Towards Data Science

I describe Spring Boot as the Apple equivalent for Software Engineering framework. 🍎 The reason is because it is well designed and it allows you to write less and clean code. I mean, it’s just elegant, like Apple. (I might be a bit biased here as I am team Apple).
Spring Boot comes with a lot of annotations that make our lives as programmers so much easier. 🙂 One of them, which I use quite frequently is the @ConfigurationProperties. I found it quite useful and so, I want to show you how to use it in your application.
More specifically, you will learn how to define your app configurations as a dataclass that will be managed by Spring. We will use the combination of the @ConfigurationProperties annotation and application.yml file to create the configuration dataclass.
Having a configuration class/es defined is convenient. We only need to define it once and after that, we can use it as many times as we want in different classes that need it.
To follow this guide along, you can clone this Github repo. Please note that the start of this tutorial is from this commit, so checkout that commit if you want to build the code yourself as you go through this tutorial. If not, you can just clone the repo and read along. 🙂
The app that is contained in the repo is a Kotlin app built by using Spring Boot and Spring Webflux and coupled with DynamoDB. If you’re interested, you can check out this article for the full walkthrough of how the application was originally built.
Let’s take a look at our current DynamoClientProperties class. You can see that it takes region and endpoint as the 2 x arguments for its class constructor. Both of these values will be injected by Spring via the @Value annotation, which looks into the application.yml file located in src/main/kotlin/resources directory, by the path passed into its argument.
On the other hand, let’s check out the content of our application.yml file. Pretty simple and straight forward. It just defines the configuration for our DynamoDB related class/es.
Now, if we just leave the application as is, everything works fine. There is really no need to change anything. But, imagine if our configuration consists of a few values, not just 2 (in our case, it’s just region and endpoint), and that same configuration is used in multiple classes, not just in 1 class (e.g. in DynamoClientProperties.kt).
When that is the case, it means that we need to write the same @Values statements in different places. That is why we are going to use @ConfigurationProperties instead. It enables us to define a dataclass containing all the configuration values for a particular purpose (for example, DynamoDB related configuration), just once, in one place and we can use it in as many other places/ classes as we need.
Go ahead and create a new Kotlin file and name it DynamoConfigProperties.kt. The content of the file will be like this.
Straight away you can see that you write less code to get the values you need. You can then go to DynamoClientProperties class and inject DynamoConfigProperties instead of region and endpoint. It will become like this.
It’s much simpler and cleaner, right? Another benefit is, if you ever need any of the values customerTableName, region or endpoint, you can simply just pass DynamoConfigProperties, which, by the way, is a Spring bean so it is only initialised once at the start of the Spring Boot application.
Now, if you run the application’s main function in DynamodemoApplication.kt from IntelliJ, everything will run as before the change was made. All is good, guys. 😃
You can go ahead and follow this tutorial to test that the interactions with DynamoDB still work as expected (it is the last part of the tutorial, sorry, but Medium does not support anchor link yet 😜).
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. 😊
If your project uses Spring Boot framework version 2.2.0.RELEASE or above, you can change the way you write DynamoConfigProperties as follows.
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.021  WARN 92292 --- [           main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dynamoConfigProperties': @EnableConfigurationProperties or @ConfigurationPropertiesScan 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 failedorg.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dynamoConfigProperties': @EnableConfigurationProperties or @ConfigurationPropertiesScan must be used to add @ConstructorBinding type io.codebrews.dynamodemo.DynamoConfigProperties 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]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).
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.
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.
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. 😜
Voila! Everything works now.
Well done, guys! 👍 👍
The application Github — here
Tutorial on how the application was originally built — here
Official Spring Boot documentation on @ConfigurationProperties — here.