Inject non-existing property via #Value from .yml file for HashMap - spring

I've got many .yml files with properties for different projects. Configuration bean is the same for all of them but some of properties might be missing for some reason. The question is how to set default NULL value to field such as HashMap via #Value. So what I'm trying is:
#Value("${property.time:#{null}}")
private Integer time;
#Value("#{${property.map:#{null}}}")
private HashMap<String,String> map;
So for first field if property is missing in .yml file then Integer becomes null which is correct.
If there is no map in .yml file I'm trying to set it to null but getting Exception:
Caused by: org.springframework.expression.spel.SpelParseException: Expression [#{null}] #1: EL1043E: Unexpected token. Expected 'identifier' but was 'lcurly({)'
p.s. Cant use spring boot in project so trying to fix it with spring framework

You could set the default value as follow :
#Value("${some.key:10}")
private int myKey;
Here it will try to look for the some.key value in the corresponding .yml file, if it is missing it will use the default value.

Related

Spring boot picking #value from default properties file

I am using #value annotation in spring boot to read a property
#Value(value = "${propName:#{null}}")
private String prop;
And based on if it is null or driving some logic in my code. In my dev environment I want to keep it null so I don't add it in that property file (application-dev.properties). But instead of getting it as null, it is reading the value from default application.properties file.
Yes, this is the correct behavior based on Spring's property resolution. If you want to override it in your dev profile, simply add the property to your application-dev.properties, leaving the value empty. This will result in an empty String, not null, which you can convert to null with something like this:
#Value("${#{some_property == \"\"}:#{null}}")
private String prop;
It is probably a good idea to explicitly define these properties and give them values that make sense. Otherwise, you'll likely revisit this code in a few months and wonder what the heck is causing xyz.
You can read up on Spring and the order with which it resolves properties here but generally, you'd want to define variables that apply to all environments in your application.[properties][yaml], and then override them in the environment-specific properties files. In this case, it kind of sounds like this is a flag that's on or off depending on if the dev environment is set; you might consider something like this.
#Value("${property_to_drive_behavior:false}"
private Boolean prop;
This will default to false, so you don't need to add it to any properties file. If you want it set, override it in the environment-specific properties file. Booleans can be set in properties files using either the explicit true or false keywords or the values or 0 for false, 1 for true.
By default, property values of external configuration sources (such as application.properties files) are always injected directly into your beans by using the #Value annotation.
One solution to your problem is moving the propName property from application.properties to application-{profile}.properties.

Spring | parameter with #value gets as value the label name and not the label value from .yaml file

on spring micro-service project - having boolean parameter like this:
#Value("${a.b.c}")
private boolean flag;
on .yaml file having:
a
b
c: true
but while running the application getting :
Caused by: java.lang.IllegalArgumentException: Invalid boolean value [${a.b.c}]
at org.springframework.beans.propertyeditors.CustomBooleanEditor.setAsText(CustomBooleanEditor.java:154)
at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:429)
while debugging looks like the parameter is getting the label name (a.b.c) and not the label value (true) and thats why fails on invalid boolean etc.
any idea whats wrong? what should be checked ? or fixed?
First off, your yaml looks wrong:
It should be (with colons):
a:
b:
c: true
Other than that, have you put these definitions into application.yaml (or yml) or you have some custom configuration?
If you put it into application.yaml the #Value itself is defined good in Java, so given the fact that the class that has this annotation on one of its field is by itself a spring bean, it should work.

#Value Integer variable in Spring Controller Causes a jUnit Error (jUnit does not use this variable)

I have a strange error in my jUnit for a SpringMVC Controller which has a #Value Integer class variable:
#Value("${max.insert.participants.batch.size}")
private Integer maxInsertParticipantsBatchSize;
The setting comes from a Properties file (all other settings are also read properly from it)
max.insert.participants.batch.size=1000
This is a variable from a Properties file. It is not used or checked in the jUnit, but the jUnit fails with:
12:38:21 Caused by: java.lang.NumberFormatException: For input string: "${max.insert.participants.batch.size}"
12:38:21 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[?:?]
As soon as I change the #Value type to a String or a Boolean the jUnit starts working. For instance
#Value("${max.insert.participants.batch.size}")
private String maxInsertParticipantsBatchSize;
Once again, the jUnit does not use this variable. It is only used in the actual application.
If your test case is a unit test then this will not work. As the spring context does not get initialized with the unit test. The way you can use the value of this variable in the unit test is by setting the variable value like this.
ReflectionUtils.setField(fieldName, object, value);
If your test case is a component test case where spring context is getting initialized then you need to supply the value in your application property file like this
max.insert.participants.batch.size:3
you can also provide a default value like this
#Value("${max.insert.participants.batch.size:3}")
private Integer maxInsertParticipantsBatchSize;
The default value will be overwritten with the value you provide in the application property file.

How to read variable or concatenate one property in #Value annotation of spring boot

I have one POJO class which read properties from application.yaml file in spring boot. Here I need to set default value to groupId(to get when not given) based on combination of topic value and another string.
private String topic;
#Value("${topic}_test")
private String groupId;
I tried this but getting error as
Could not resolve placeholder 'topic' in value "${topic}_test
I create a variable and tried to access it in #Value but failed.
Please provide any suggestion to access this
Default value
Let's say you have a property like this in your application.yml file
topic: myFavoriteTopic
The code below will assign the value "myFavoriteTopic" to variable groupId if Spring is able to load the property with key topic from application.yml. If it's not, it will assign the default value "my default topic".
#Value("${topic:my default topic}")
private String groupId;
To set null as the default value, use:
#Value("${topic:#{null}}")
To use an empty String as the default value, use:
#Value("${topic:}")
Get key name from a variable
In your code snippet you have a String topic variable. For the Spring framework, this variable is not used in the property value retrieval. When you do #Value("${topic}"), "topic" is the name of the key that Spring will look for in application.yml. The name of the key cannot be determined at runtime based on the value of a Java variable. The reason is that properties can be loaded before the class code executes.
_test suffix
Also in your code snippet, you use #Value("${topic}_test"). What Spring does in this case is to concatenate "_test" to the value retrieved for property key topic. In my first example, groupId would be assigned "myFavoriteTopic_test".

Optional environment variables in Spring app

In my Spring Boot app's application.properties I have this definition:
someProp=${SOME_ENV_VARIABLE}
But this is an optional value only set in certain environments, I use it like this
#Value("${someProp:#{null}}")
private String someProp;
Surprisingly I get this error when the env. var doesn't exist
Could not resolve placeholder 'SOME_ENV_VARIABLE' in string value "${SOME_ENV_VARIABLE}"
I was expecting Spring to just set a blank value if not found in any PropertySource.
How to make it optional?
Provide a default value in the application.properties
someProp=${SOME_ENV_VARIABLE:#{null}}
When used like #Value("${someProp}), this will correctly evaluate to null. First, if SOME_ENV_VARIABLE is not found when application.properties is being processed, its value becomes the string literal "#{null}". Then, #Value evaluates someProp as a SpEL expression, which results in null. The actual value can be verified by looking at the property in the Environment bean.
This solution utilizes the default value syntax specified by the PlaceholderConfigurerSupport class
Default property values can be defined globally for each configurer
instance via the properties property, or on a property-by-property
basis using the default value separator which is ":" by default and
customizable via setValueSeparator(String).
and Spring SpEL expression templating.
From Spring Boot docs on externalized configuration
Finally, while you can write a SpEL expression in #Value, such
expressions are not processed from Application property files.
This work for me:
spring.datasource.url=jdbc:mysql://${DB_IP:localhost}:3306/app
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:mylocaluser}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:localpass}
Because this is probably interesting to others who come here, you can override any properties file w/ an env variable implicitly. Let's say you have property.
someapp.foo
Then you can define an env variable SOMEAPP_FOO (capital letters and . -> _ ) and spring will implicitly set the property from the env. variable.
Described further here: https://hughesadam87.medium.com/how-to-override-spring-properties-with-env-vars-82ee1db2ae78

Resources