1. Overview
One common requirement in Spring Boot applications is to inject external configuration properties into Spring Beans. The @ConfigurationProperties annotation allows us to bind a set of configuration properties into a class. Alternatively, we can leverage the @Value annotation to inject arbitrary configuration properties into a Spring Bean.
In this quick tutorial, we’ll explore how to inject configuration properties using @Value.
2. Our YAML Configuration File
Let’s create a simple YAML file named application-inject-value.yml under src/main/resources as our application’s configuration:
my-app:
magic-number: 42
magic-string: "It's a magic string"
magic-flag: true
magic-string-with-default: "It's another magic string"
To make @PropertySource load our YAML configuration file, we create the YamlPropertySourceFactory class:
class YamlPropertySourceFactory : PropertySourceFactory {
override fun createPropertySource(@Nullable name: String?, encodedResource: EncodedResource): PropertySource<*> =
PropertiesPropertySource(encodedResource.resource.filename,
YamlPropertiesFactoryBean().also { it.setResources(encodedResource.resource) }.getObject()
)
}
Then, we can use the @PropertySource annotation to parse our YAML configuration:
@PropertySource(value = ["classpath:application-inject-value.yml"], factory = YamlPropertySourceFactory::class)
@SpringBootApplication(scanBasePackages = ["com.baeldung.theValueAnnotation"])
class KotlinValueInjectionApplication
Next, we’ll create a Spring component and inject configuration properties based on this YAML file.
3. Injecting Configuration Properties
When we work with @Value in Java, we can inject configuration property in this way: @Value(“${prop.name.from.config}”).
In Kotlin, we adhere to a similar approach. However, due to Kotlin’s String template using ‘*$*‘ to reference variables or expressions, we must escape ‘*$*‘ within the @Value annotation. Now, let’s create a Spring component and inject several properties from our application-inject-value.yml:
@Component
class ValueBean(
@Value("\${my-app.magic-number}")
val magicNumber: Int,
@Value("\${my-app.magic-string}")
val magicString: String,
@Value("\${my-app.magic-flag}")
val magicFlag: Boolean,
)
As the ValueBean class shows, we applied the constructor injection approach to inject three configuration properties.
Next, we’ll verify whether these @Value annotations work as expected in a unit test. Let’s start with creating a test class:
@SpringBootTest
@TestConstructor(autowireMode = AutowireMode.ALL)
class TheValueAnnotationUnitTest(val valueBean: ValueBean) {
...
}
We used the @TestConstructor annotation to implement constructor injection for injecting the ValueBean Spring component within the test class. Subsequently, we can conduct verifications within a test function:
with(valueBean) {
assertEquals(42, magicNumber)
assertEquals("It's a magic string", magicString)
assertTrue(magicFlag)
}
The test passes if we run it. That is, these three configuration properties have been injected into the ValueBean bean as expected.
4. @Value With Non-Null Default Values
Sometimes, we may wish to offer a default value for situations where a property might be absent in the configuration file. To accomplish this, we can adopt the @Value(“\${prop.name:defaultValue”) pattern.
Now, let’s extend our ValueBean component by injecting two new properties with default values:
@Component
class ValueBean(
...
// with default values
@Value("\${my-app.not-defined-value:1024}")
val magicNumberWithDefault: Int,
@Value("\${my-app.magic-string-with-default:default Value}")
val magicStringWithDefault: String,
}
In the code above, both @Value injections are with default values.
Looking at the configuration file, my-app.not-defined-value is absent, while my-app.magic-string-with-default is explicitly defined. As a result, the default value 1024 is applied to magicNumberWithDefault, and the specified value is injected into magicStringWithDefault:
with(valueBean) {
assertEquals(1024, magicNumberWithDefault)
assertEquals("It's another magic string", magicStringWithDefault)
}
5. @Value With null as the Default Value
We’ve learned how to set non-null values as defaults when using @Value injections. Occasionally, when a property is missing in configuration files, we might prefer to treat null as the default.
Let’s further extend ValueBean to cover this case:
@Component
class ValueBean(
...
// with null as the default value
@Value("\${my-app.not-defined-value:null}")
val stringDefaultLiteralNull: String?,
@Value("\${my-app.not-defined-value:#{null}}")
val stringDefaultNull: String?
)
As Kotlin has nullable and non-nullable types, we must declare the fields nullable if we want their defaults to be null.
We can see that although both @Value attempt to inject non-existing properties and introduce defaults, one uses “prop.name:null”, and the other one employs “*prop.name:#{null}*“.
Next, let’s look at the outcome:
with(valueBean) {
assertEquals("null", stringDefaultLiteralNull)
assertNull(stringDefaultNull)
}
The test shows their difference clearly:
- prop.name:null – The literal String “null” is the default.
- prop.name:#{null} – The default value is null. Because #{null}* is a Spring Expression (SpEL) that evaluates to *null.
6. Conclusion
In this article, we explored using Spring’s @Value for injecting configuration properties in Kotlin, including how to introduce defaults for absent properties in the configuration file.
As always, the complete source code for the examples is available over on GitHub.