Spring Boot 2.x binding YAML list into class problems - spring

So I have been trying to a list property from my YAML to a class that I made and somehow the list binding can only recognize comma-separated values from the YAML.
Given this class
#Getter
#Configuration
#ConfigurationProperties
#PropertySource("classpath:test.yml")
public class SomeProperty {
final List<String> someList = new ArrayList<>();
}
And this yaml file
someList:
- a
- b
If I autowire the SomeProperty somewhere and print the someList, it will return an empty list, but if I change the yaml file to this
someList: a,b
Then the binding works and returns an array list containing the values. What is the problem with the binding?

Just found out that PropertySource can only recognize *.properties files that is why it cannot recognize yaml lists.

Related

How to load List from YAML in spring boot 2.6 [duplicate]

I am trying to load an array of strings from the application.yml file. This is the config:
ignore:
filenames:
- .DS_Store
- .hg
This is the class fragment:
#Value("${ignore.filenames}")
private List<String> igonoredFileNames = new ArrayList<>();
There are other configurations in the same class that loads just fine. There are no tabs in my YAML file. Still, I get the following exception:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'ignore.filenames' in string value "${ignore.filenames}"
use comma separated values in application.yml
ignoreFilenames: .DS_Store, .hg
java code for access
#Value("${ignoreFilenames}")
String[] ignoreFilenames
It is working ;)
My guess is, that the #Value can not cope with "complex" types. You can go with a prop class like this:
#Component
#ConfigurationProperties('ignore')
class IgnoreSettings {
List<String> filenames
}
Please note: This code is Groovy - not Java - to keep the example short! See the comments for tips how to adopt.
See the complete example https://github.com/christoph-frick/so-springboot-yaml-string-list
From the spring boot docs https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
YAML lists are represented as property keys with [index] dereferencers, for example this YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
Would be transformed into these properties:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
To bind to properties like that using the Spring DataBinder utilities (which is what #ConfigurationProperties does) you need to have a property in the target bean of type java.util.List and you either need to provide a setter, or initialize it with a mutable value, e.g. this will bind to the properties above. Here is what the question's code would look like.
#ConfigurationProperties(prefix="ignore")
public class Filenames {
private List<String> ignoredFilenames = new ArrayList<String>();
public List<String> getFilenames() {
return this.ignoredFilenames;
}
}
In addition to Ahmet's answer you can add line breaks to the coma separated string using > symbol.
application.yml:
ignoreFilenames: >
.DS_Store,
.hg
Java code:
#Value("${ignoreFilenames}")
String[] ignoreFilenames;
Well, the only thing I can make it work is like so:
servers: >
dev.example.com,
another.example.com
#Value("${servers}")
private String[] array;
And dont forget the #Configuration above your class....
Without the "," separation, no such luck...
Works too (boot 1.5.8 versie)
servers:
dev.example.com,
another.example.com
In my case, this was a syntax issue in the .yml file. I had:
#Value("${spring.kafka.bootstrap-servers}")
public List<String> BOOTSTRAP_SERVERS_LIST;
and the list in my .yml file:
bootstrap-servers:
- s1.company.com:9092
- s2.company.com:9092
- s3.company.com:9092
was not reading into the #Value-annotated field. When I changed the syntax in the .yml file to:
bootstrap-servers: >
s1.company.com:9092,
s2.company.com:9092,
s3.company.com:9092
it worked fine.
#Value("#{'${your.elements}'.split(',')}")
private Set<String> stringSet;
yml file:
your:
elements: element1, element2, element3
There is lot more you can play with spring spEL.
Ahmet's answer provides on how to assign the comma separated values to String array.
To use the above configuration in different classes you might need to create getters/setters for this..
But if you would like to load this configuration once and keep using this as a bean with Autowired annotation, here is the how I accomplished:
In ConfigProvider.java
#Bean (name = "ignoreFileNames")
#ConfigurationProperties ( prefix = "ignore.filenames" )
public List<String> ignoreFileNames(){
return new ArrayList<String>();
}
In outside classes:
#Autowired
#Qualifier("ignoreFileNames")
private List<String> ignoreFileNames;
you can use the same list everywhere else by autowiring.
Configuration in yaml file:
ignore:
filenames: >
.DS_Store
.hg
In spring component:
#Value("#{'${gnore.filenames}'.split(' ')}")
private List<String> igonoredFileNames;
This worked fine for me.
#Value("${your.elements}")
private String[] elements;
yml file:
your:
elements: element1, element2, element3

Spring does not load Map of objects from YAML, if the key String contains dots [duplicate]

I have a following yml config:
foo:
bar.com:
a: b
baz.com:
a: c
With a following class Spring tries to inject map with keys 'bar' and 'baz', treating dots as separator:
public class JavaBean {
private Map<String, AnotherBean> foo;
(...)
}
I have tried quoting the key (i.e. 'bar.com' or "bar.com") but to no avail - still the same problem. Is there a way around this?
A slight revision of #fivetenwill's answer, which works for me on Spring Boot 1.4.3.RELEASE:
foo:
"[bar.com]":
a: b
"[baz.com]":
a: c
You need the brackets to be inside quotes, otherwise the YAML parser basically discards them before they get to Spring, and they don't make it into the property name.
This should work:
foo:
"[bar.com]":
a: b
"[baz.com]":
a: c
Inspired from Spring Boot Configuration Binding Wiki
This is not possible if you want automatic mapping of yaml keys to Java bean attributes. Reason being, Spring first convert YAML into properties format.
See section 24.6.1 of link below:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
So, your YAML is converted to:
foo.bar.com.a=b
foo.baz.com.a=c
Above keys are parsed as standard properties.
As a work around, you can use Spring's YamlMapFactoryBean to create a Yaml Map as it is. Then, you can use that map to create Java beans by your own.
#Configuration
public class Config {
private Map<String, Object> foo;
#Bean
public Map<String, Object> setup() {
foo = yamlFactory().getObject();
System.out.println(foo); //Prints {foo={bar.com={a=b}, baz.com={a=c}}}
return foo;
}
#Bean
public YamlMapFactoryBean yamlFactory() {
YamlMapFactoryBean factory = new YamlMapFactoryBean();
factory.setResources(resource());
return factory;
}
public Resource resource() {
return new ClassPathResource("a.yaml"); //a.yaml contains your yaml config in question
}
}

How to define a conditional property with a variable sub property name

I have a configuration property class that contains a map. The map is filled with values from a yml file. the structure looks like this.
config:
variable_sub_key_a:
key_a: 1234
key_b: 1234
variable_sub_key_b:
...
The configuration class looks like:
#Configuration
public class Configuration{
private Map<String, KEY> config= new HashMap<>();
}
I would like that the configuration class is only initialized by spring when the config element exists in the yml file. My problem is that a part of the property name can vary. So i can't use #ConditionalOnProperty. I tried it with different contidional-annotations but haven't found out how i could define a conditional that checks only that the property starts with config.* . Any hints how i can do this?

Spring Custom Configuration not populating properties with underscore

I am trying to populate Custom class with properties. following is my Custom Properties class:
#Configuration
#ConfigurationProperties(prefix = "foo.bar")
public class CustomProperties{
private String PROPERTY_ONE;
private String propertyTwo;
//setters
//getters
}
and my properties in application.properties are:
foo.bar.PROPERTY_ONE=some text
foo.bar.PROPERTY_TWO=some other text
When I am trying to use value from CustomProperties this is what I gets:
customProperties.getPROPERTY_ONE() = null
customProperties.getPopertyTwo() = some other text
So I observed that if I have variable name with underscore(_) in it not populating the property value.
is there any way to get the value with variable having underscore?
Yes, it is 100% possible to get your configuration values.
It's all about the casing! Inside of CustomProperties simply name your first property propertyOne ... and refactor your getters/setters appropriately ... and you'll be good to go!
Spring's got the camel casing going on when translating the configuration fields to your Configuration classes/properties. So instead of matching the casing of your properties, follow the camel casing equivalent of the property name found in your configuration file.
Example: PROPERTY_ONE translates to propertyOne

Spring: #NestedConfigurationProperty List in #ConfigurationProperties

Hi I am trying to get the following configuration up and running.
#ConfigurationProperties(prefix="my")
public class Config {
#NestedConfigurationProperty
private List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>();
public List<ServerConfiguration> getServers() {
return this.servers;
}
}
#ConfigurationProperties(prefix = "server")
public class ServerConfiguration {
private String name;
private String description;
}
So, I want to have multiple server configs nested in objects.
I tried setting the properties with the following properties file. I can see that the list is added up by items but the members of the server are never set (name, description)
my.servers[0].name=test
my.servers[0].server.name=test
my.servers[1].name=test
my.servers[1].server.name=test
To extend what Maciej said already.
#ConfigurationProperties should be set only on root objects (that is objects that are responsible to handle a given prefix. There is no need to annotated nested objects with it.
#NestedConfigurationProperty is only used by the meta-data generator (to indicate that a property is not a single value but something we should explore to generate additional meta-data. In the case of the List there isn't any finite amount of properties so the meta-data will have to stop at the list.
In any case you need a getter and a setter for each singular property. We don't do field binding and we require a public getter to avoid exposing unnecessary properties in the meta-data.
You need to add setters and getters to ServerConfiguration
You don't need to annotate class with nested properties with #ConfigurationProperties
There is a mismatch in names between ServerConfiguration.description and property my.servers[X].server.name=test

Resources