Kotlin & Spring Boot #ConfigurationProperties - spring

How to properly initialize ConfigurationProperties in Spring Boot with Kotlin?
Currently I do like in the example below:
#ConfigurationProperties("app")
class Config {
var foo: String? = null
}
But it looks pretty ugly and actually foo is not a variable, foo is constant value and should be initialized during startup and will not change in the future.

With new Spring Boot 2.2 you can do like so:
#ConstructorBinding
#ConfigurationProperties(prefix = "swagger")
data class SwaggerProp(
val title: String, val description: String, val version: String
)
And don't forget to include this in your dependencies in build.gradle.kts:
dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}

Here is how I have it working with my application.yml file.
myconfig:
my-host: ssl://example.com
my-port: 23894
my-user: user
my-pass: pass
Here is the kotlin file:
#Configuration
#ConfigurationProperties(prefix = "myconfig")
class MqttProperties {
lateinit var myHost: String
lateinit var myPort: String
lateinit var myUser: String
lateinit var myPass: String
}
This worked great for me.

Update: As of Spring Boot 2.2.0, you can use data classes as follows:
#ConstructorBinding
#ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
val name: String,
val description: String,
val myService: MyService) {
data class MyService(
val apiToken: String,
val uri: URI
)
}
For further reference, see the official documentation.
Obsolete as of Spring Boot 2.2.0, Issue closed
As stated in the docs: A "Java Bean“ has to be provided in order to use ConfigurationProperties. This means your properties need to have getters and setters, thus val is not possible at the moment.
Getters and setters are usually mandatory, since binding is via standard Java Beans property descriptors, just like in Spring MVC. There are cases where a setter may be omitted [...]
This has been resolved for Spring Boot 2.2.0, which is supposed to be released soon:
https://github.com/spring-projects/spring-boot/issues/8762

On Spring Boot 2.4.3 with Kotlin 1.4.3 the next approach is no longer working (maybe because of a bug):
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
#SpringBootApplication
#EnableConfigurationProperties(TestProperties::class)
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
#ConfigurationProperties(prefix = "test")
#ConstructorBinding
data class TestProperties(
val value: String
)
The code above starts working after implying one of the next two approaches:
Add dependency
implementation("org.jetbrains.kotlin:kotlin-reflect")
Update Properties class
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
#ConfigurationProperties(prefix = "test")
data class TestProperties #ConstructorBinding constructor(
val value: String
)
The problem happens at the line org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java#68

#Value("\${some.property.key:}")
lateinit var foo:String
could be used this way

#ConstructorBinding
#ConfigurationProperties(prefix = "your.prefix")
data class AppProperties (
val invoiceBaseDir: String,
val invoiceOutputFolderPdf: String,
val staticFileFolder: String
)
Don't forget to add #ConfigurationPropertiesScan
#ConfigurationPropertiesScan
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
And finally the application.properties file:
your.prefix.invoiceBaseDir=D:/brot-files
your.prefix.invoiceOutputFolderPdf=invoices-pdf
your.prefix.staticFileFolder=static-resources

As of Spring Boot 3.0 you don't need to use #ConstructorBinding annotation anymore.
#ConfigurationProperties("app")
data class Config(
val foo: String = "default foo"
)
more info here

application.properties
metro.metro2.url= ######
Metro2Config.kt
#Component
#ConfigurationProperties(prefix = "metro")
data class Metro2PropertyConfiguration(
val metro2: Metro2 = Metro2()
)
data class Metro2(
var url: String ?= null
)
build.gradle
Plugins:
id 'org.jetbrains.kotlin.kapt' version '1.2.31'
// kapt dependencies required for IntelliJ auto complete of kotlin config properties class
kapt "org.springframework.boot:spring-boot-configuration-processor"
compile "org.springframework.boot:spring-boot-configuration-processor"

This is how I did it:
application.properties
my.prefix.myValue=1
MyProperties.kt
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component
#Component
#ConfigurationProperties(prefix = "my.prefix")
class MyProperties
{
private var myValue = 0
fun getMyValue(): Int {
return myValue;
}
fun setMyValue(value: Int){
myValue = value
}
}
MyService.kt
#Component
class MyService(val myProperties: MyProperties) {
fun doIt() {
System.console().printf(myProperties.getMyValue().toString())
}
}

In addition to what's already been said, note that val and #ConstructorBinding has some limitations. You cannot alias one variable to another. Let's say you're running in Kubernetes and want to capture the hostname, which is given by the env var HOSTNAME. The easiest way to do this is to apply #Value("\${HOSTNAME}:)" to a property, but it only works for a mutable property and without constructor binding.
See #ConstructorBinding with immutable properties don't work with #Value in Spring Boot Kotlin #ConfigurationProperties.

Related

Kotlin constructor binding is not applicable to target 'class'

i am new to Kotlin. I am trying to achieve same thing, i would do in java.
in application.yaml, i have this config:
conference:
plans:
- plan-id: P1_1
product-id: product1
employee-limit: 50
- plan-id: P1_2
product-id: product2
employee-limit: 100
then i want to create data class, so that , when i run the springboot application, this class is automatically loaded and validated
in Conferences.kt file:
package com.company.conferenceplanservice.config.properties
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotEmpty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.ConstructorBinding
import org.springframework.context.annotation.Configuration
import org.springframework.validation.annotation.Validated
#Validated
#ConstructorBinding
#Configuration
#ConfigurationProperties(prefix = "conference")
data class Plans(
#field:NotEmpty val plans: Set<Plan>
)
#Validated
#ConstructorBinding
data class Plan(
#field:NotBlank val planId: String,
#field:NotBlank val productId: String,
val eomployeeLimit: Int
)
but always it throws this exception, on both places where ConstructorBinding is written
Kotlin: This annotation is not applicable to target 'class'
springboot 3.0.0, java 18, kotlin 1.7.21
I hope this helps
I no longer use #ConstructingBinding nowadays
This is How I would have implemented
application.yaml
conference:
plan-id: P1_1
product-id: product1
Conferences.kt
import org.springframework.boot.context.properties.ConfigurationProperties
#ConfigurationProperties(prefix = "conference")
data class Conferences(
val planId: String,
val productId: String,
)
Usage.kt
object MySample {
fun haveFun(conference : Conference) : String {
return conference.planId
}
}
#Component
class BootLoader(val conference: Conference) : CommandLineRunner {
override fun run(vararg args: String?) {
println(MySample.haveFun(conference))
}
}
FYI
"#ConstructingBinding No Longer Needed at the Type Level"
"Improved #ConstructorBinding Detection When using constructor bound #ConfigurationProperties the #ConstructorBinding annotation is no longer required if the class has a single parameterized constructor. If you have more than one constructor, you’ll still need to use #ConstructorBinding to tell Spring Boot which one to use."
"For most users, this updated logic will allow for simpler #ConfigurationProperties classes. If, however, you have a #ConfigurationProperties and you want to inject beans into the constructor rather than binding it, you’ll now need to add an #Autowired annotation."
reference
https://github.com/spring-projects/spring-boot
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConstructorBinding.html
#Validated
#ConstructorBinding
#Configuration
#ConfigurationProperties(prefix = "conference")
data class Plans(
#field:NotEmpty val plans: Set<Plan>
)
#Validated
#ConstructorBinding
data class Plan(
#field:NotBlank val planId: String,
#field:NotBlank val productId: String,
val eomployeeLimit: Int
)
and #ConfigurationPropertiesScan
annotation is needed in main class
I don't use Spring, but I'm guessing #ConstructorBinding is an annotation for a constructor. You are using it on the class declaration itself. It would be like putting it in front of the class name in Java:
#ConstructorBinding
public class Plans {
//...
}
If you want to annotate the data class constructor in Kotlin, you have to declare the constructor. You have omitted the constructor keyword, which is usually fine, but not if you need to annotate it or modify its visibility:
#Validated
#Configuration
#ConfigurationProperties(prefix = "conference")
data class Plans
#ConstructorBinding constructor(
#field:NotEmpty val plans: Set<Plan>
)

spring boot startup process failed with lateinit var not initialized : DI Issue

I'm buidling a backend that will fetch some data from several externals APIs, to populate DB after some processes, and then expose these data via a rest api. Using JPA and postgresql, in springboot.
I have created the entities, repositories and fetch external apis from a webclient.
However, I have a dependency injection issue. I read tons of articles and issues, but can not make it work. When trying to inject the repository, I got the well known error lateinit var not initialized.
I also tried constructor injection, but still doesn't work. It seems that the repository is not considered to be a bean that could be autowired.
Any help would be appreciated
FIXED SEE SOLUTION BELOW. PB WAS IN THE SERVICE
Application. Running the startuprocess from the context
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val startupProcess = context.getBean(StartupProcesses::class.java)
startupProcess.fetchGroup()
}
startup class as #component :fetches external api and calls a service to save data in db
#Component
class StartupProcesses {
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
SplitDataSourceToEntities().populateGroup(fetch) //<-- call service to populate db
}
}
Service that should populates the DB through repository but error in lateinit repo
#Service
class SplitDataSourceToEntities {
#Autowired
lateinit var repo:IronMaidenGroupRepository // <-- error is here
fun populateGroup(dto: DTOIronMaidenAPi): IronMaidenGroupEntity {
val dbgroup = IronMaidenGroupEntity(
groupId = dto.id!!,
name = dto.name ?: ""
)
return repo.save(dbgroup)
}
}
the repo, extending JPA repository
import com.jerome.springbootkotlin.model.dbentities.ironmaidengroupentity.IronMaidenGroupEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
#Repository
interface IronMaidenGroupRepository:JpaRepository<IronMaidenGroupEntity, String> {
}
SOLUTION
Service should be defined like this (the SplitDataSourceToEntitiesclass should also be late init instead of beeing instantiated)
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities // <--add this lateinit
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource()
splitDataSourceToEntities.populateGroup(fetch) //<-- fix is here
}
}
#Autowired works only when managed by Spring
It is important to note, that #Autowired does only work when the class has been initialized by Spring. In your code, you have an #Autowired annotation in class SplitDataSourceToEntities, but you manually instantiate SplitDataSourceToEntities in StartupProcesses.fetchGroup. That cannot work, because Spring had no possibility to auto-wire the lateinit var.
The problem can easily be solved by using an autowired instance of your service:
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
splitDataSourceToEntities.populateGroup(fetch) //<-- call service to populate db
}
}
Help Spring finding your JpaRepository
Additionally, you probably need to add an #EnableJpaRepositories annotation to your application:
#EnableJpaRepositories(basePackageClasses = [IronMaidenGroupRepository::class])
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
...
Instead of basePackageClasses = ... you can just define the package directly by name, but the package name is not included in your example.
Do you have to use #Autowired at all?
Considering your question in the comments:
There is nothing wrong with your design, because it is not not necessary to "play" with #Component and #Autowired. You could as well get rid of the #Autowired annotations and define those variables as, e.g., constructor parameters.
That could look like this:
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val repo = context.getBean(IronMaidenGroupRepository::class.java)
StartupProcesses(SplitDataSourceToEntities(repo))
}
#Component
class StartupProcesses(
val splitDataSourceToEntities: SplitDataSourceToEntities
) {
...
}
#Service
class SplitDataSourceToEntities(
val repo: IronMaidenGroupRepository
) {
...
}
Now only your Repository is managed by Spring, but on the flip-side you have to manage everything else by yourself which might get very tedious when your classes and their dependencies grow. It is much more comfortable (and in the end leads to better readable code) to let just Spring manage all the dependencies.

Spring Kotlin #ConfigurationProperties for data class defined in dependency

I've got a library that has a configuration class (no spring configuration class) defined as a data class. I want a Bean of that configuration which can be configured via application.properties. The problem is that I don't know how to tell Spring to create ConfigurationProperties according to that external data class. I am not the author of the configuration class so I can't annotate the class itself. #ConfigurationProperties in conjunction with #Bean does not work as the properties are immutable. Is this even possible?
Maybe change scan packages to inlcude the packages that do you want.
#SpringBootApplication( scanBasePackages = )
take a look this:
Configuration using annotation #SpringBootApplication
If I understand correctly, do you need a way to turn a third-party object into a bean with properties from your application.properties file ?
Given an application.properties file:
third-party-config.params.simpleParam=foo
third-party-config.params.nested.nestedOne=bar1
third-party-config.params.nested.nestedTwo=bar2
Create a class to receive your params from properties file
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
#Configuration
#ConfigurationProperties(prefix = "third-party-config")
data class ThirdPartConfig(val params: Map<String, Any>)
Here is an example of the object that you want to use
class ThirdPartyObject(private val simpleParam: String, private val nested: Map<String, String>) {
fun printParams() =
"This is the simple param: $simpleParam and the others nested ${nested["nestedOne"]} and ${nested["nestedTwo"]}"
}
Create the configuration class with a method that turns your third-party object into an injectable bean.
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
#Configuration
class ThirdPartObjectConfig(private val thirdPartConfig: ThirdPartConfig) {
#Bean
fun thirdPartyObject(): ThirdPartyObject {
return ThirdPartObject(
simpleParam = thirdPartConfig.params["simpleParam"].toString(),
nested = getMapFromAny(
thirdPartConfig.params["nested"]
?: throw IllegalStateException("'nested' parameter must be declared in the app propertie file")
)
)
}
private fun getMapFromAny(unknownType: Any): Map<String, String> {
val asMap = unknownType as Map<*, *>
return mapOf(
"nestedOne" to asMap["nestedOne"].toString(),
"nestedTwo" to asMap["nestedTwo"].toString()
)
}
}
So now you can inject your third-party object as a bean with custom configurated params from your application.properties files
#SpringBootApplication
class StackoverflowAnswerApplication(private val thirdPartObject: ThirdPartObject): CommandLineRunner {
override fun run(vararg args: String?) {
println("Running --> ${thirdPartObject.printParams()}")
}
}

How to get variable from spring application.yaml in kotlin

I have app.name in my application.yaml
How do i access this please, I was hoping for a simple one liner.
#Values doesn't seem to work, it only works in my restcontroller. I've created a configurationproperties and a EnableConfigurationProperties, but no one will tell me how to actually get a value from the properties.
class databaseHandler() {
#Value("\${app.url}")
private val url: String? = null
fun dave(){
print(url)
} ==Null
==
#Component
#ConfigurationProperties("app")
class appConfig(){
lateinit var url: String
}
#EnableConfigurationProperties(appConfig::class)
class dave(){
fun dave(){
print(appConfig().url)
} ==Lateinit url hasn't been initalized
}
Figured out the issue, You need to initialise your sub-class as well and also declare it as a spring component e.g.
#Service
class DatabaseHelper {
#Value("\${app.url}")
val url= ""
}
#RestController
#RequestMapping("/")
class GitHubController{
#Autowired
lateinit var databaseHelper : DatabaseHelper
#GetMapping("")
fun hello() = "hello $databaseHelper.url"
}
Assuming application.yml has the following properties:
app-url: www.demo-url.com
Add#EnableConfigurationProperties to your binding class i.e.
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
class appConfig() {
lateinit var url: String
}
#EnableConfigurationProperties this annotation is used to enable #ConfigurationProperties annotated beans in the Spring application
Please refer: https://www.baeldung.com/spring-yaml

Spring Boot with Kotlin - #Value annotation not working as expected

I'm developing a Spring boot application using Kotlin. Since I need to connect to an external API (cloudinary) I decided to add to my app a configuration class in order to store (and hide from VCS) my sensible data like username, passwords or API keys.
So this is what i did:
I created a Config class:
package demons
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.PropertySource
#Configuration
#PropertySource("classpath:application.properties")
class AppConfig {
#Value("\${test.prop}")
val testProperty: String? = null
}
Then I added a test.prop entry in my application.properties file
test.prop=TEST
However, in every test I run, after creating an instance of AppConfig, his testProperty attribute is null instead of being the string TEST.
For instance this snippet:
val config = AppConfig()
System.out.println(config.testProperty)
would print out:
null
I've also tried using a separate .properties file instead of the default one like myproperties.properties and declaring the variable as lateinit var. In this last case the variable seems to never be initialized:
kotlin.UninitializedPropertyAccessException: lateinit property testProperty has not been initialized
What am I missing?
The issue is that you are creating the instance of AppConfig yourself via the constructor:
val config = AppConfig()
While this class might have Spring annotations, it is NOT spring managed if you create the instance yourself.
I recommend you borrow from the link you mentioned in my other answer. There are good examples of using SpringBoot to create a Spring application for you. Below I created a 'merged' example of your test + an example from the link. There is no need to specify a properties file, as application.properties is used as a property source by default.
#SpringBootApplication
class AppConfig {
#Value("\${test.prop}")
val testProperty: String? = null
}
fun main(args: Array<String>) {
val appContext = SpringApplication.run(AppConfig::class.java, *args)
val config = appContext.getBean(AppConfig::class.java)
System.out.println(config.testProperty)
}

Resources