I have come across some behaviour which seems inconsistent in how spring boot handles environment variables in application properties files vs configuration property classes. I am unsure whether this is a bug in spring or an error in my understanding of what "ought" to happen.
I have
#Data
#ConfigurationProperties("foo")
#Validated
public class ClientProperties {
#NotBlank
private String apiKey;
#NotBlank
private String uri;
}
In the application.properties file I have:
foo.baseUri=https://system.appspot.com
foo.uri=${foo.baseUri}/Valuation?apikey=${foo.apiKey}&bar={bar}
Setting Just FOO_APIKEY
If I run my app with:
export FOO_APIKEY=DEF
Then I get
APPLICATION FAILED TO START
***************************
Description:
Binding to target class ClientProperties(apiKey=null, uri=https://system.appspot.com/Valuation?apikey=DEF&bar={bar}) failed:
Property: foo.apiKey
Value: null
Reason: may not be empty
Note that in the URI the api key is set as expected as well as the base URI
Setting Just FOO_API_KEY
Next, if instead, I try to set just this property (remove the old env var):
export FOO_API_KEY=ABC
Then my app starts, but the values are not as expected. My logs show:
API Key: ABC.
URI Property: ${foo.baseUri}/Insurance?apikey=${foo.apiKey}&bar={bar}.
Note that now the base uri also disappeared as well as the API key being missing.
Setting Both Properties FOO_API_KEY and FOO_APIKEY
When I set both environment variables the app starts but the apiKey property of ClientProperties holds the value of the FOO_APIKEY export, where as the uri property of ClientProperties holds the value of the FOO_API_KEY export.
API KEY IS: ABC.
URI IS: https://system.appspot.com/Insurance?apikey=DEF&bar={bar}.
Notes
I actually don't need the value from ClientProperties.apiKey. It's only ever used in the app via the ClientProperties.uri which is already being resolved in application.properties. However, I specify the property so that I can have validation to ensure the value gets set. I could remove the value from my class and everything would be ok - expect then I lose my validation.
Spring boot version is: 1.5.10.RELEASE
Related
I have a Maven/SpringBootApplication that takes its properties from a Spring config Server. I need to override the values of these properties using command line arguments. unfortunately, the properties keep the values provided by the config server and are not overridden by the command line arguments.
I have confirmed that the parameters are properly passed to the App as I can see being passed to SpringApplication.run.
I can see in the function ConfigurableApplicationContext of Spring Framework the environment carrying the arguments in environment.propertysources.propertySourceList.SimpleCommandLinePropertySource.source.optionArgs
If I try to set a Spring-defined value (e.g. --logging.level.org.springframework.web=TRACE) it works, meaning Spring logs traces
I read all possible threads on the subject but none seem to apply to my problem.
This is my Spring boot app (args are beeing passed to the SpringApplication)
#SpringBootApplication
#ComponentScan("com.mycompany")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Here is the component and the property
#Component
public class TaskProcessor implements com.mycompnay.fwk.task.engine.TaskProcessor {
private RestTemplate restTemplate = new RestTemplate();
#Value("${mycompany.converter.converter-uri.office}")
private String converterUriOffice;
}
The parameter being passed is received by the app (extracted from debugger):
0:"--debug=true"
1:"--logging.level.org.springframework.web=TRACE"
2:"--mycompany.converter.converter-uri.office=foo"
hash:0
value:char[44]#25
I expect the property converterUriOffice to have the value foo
Instead it gets its value from the config server (http://localhost:3000/convert/office)
Links from Devilluminati did the job. Thanks a lot! To make it as clear as possible, here is what I had to do.
1- My application has a matching YML file served by the config server called application.yml
2- Inside the application.yml, I have two profiles and I only wanted the ability to override the arguments while using the local profile.
So here is what I had to add to application.yml:
spring:
profiles: local
cloud:
config:
override-system-properties: false
Once I did that (and restarted the config server to make sure it pulls the latest YML), I am able to override the value above by passing the following to the command line:
--mycompany.converter.converter-uri.office=foo
found following in the documentation https://cloud.spring.io/spring-cloud-static/Edgware.SR2/single/spring-cloud.html#overriding-bootstrap-properties
Overriding the Values of Remote Properties The property sources that
are added to you application by the bootstrap context are often
"remote" (e.g. from a Config Server), and by default they cannot be
overridden locally, except on the command line. If you want to allow
your applications to override the remote properties with their own
System properties or config files, the remote property source has to
grant it permission by setting spring.cloud.config.allowOverride=true
(it doesn’t work to set this locally). Once that flag is set there are
some finer grained settings to control the location of the remote
properties in relation to System properties and the application’s
local configuration: spring.cloud.config.overrideNone=true to override
with any local property source, and
spring.cloud.config.overrideSystemProperties=false if only System
properties and env vars should override the remote settings, but not
the local config files.
same problem here with the solution https://github.com/spring-cloud/spring-cloud-config/issues/907
The documentation is not very clear cause it says that command line
arguments always take precedence over remote configuration.
The property sources that are added to you application by the
bootstrap context are often "remote" (e.g. from a Config Server), and
by default they cannot be overridden locally, except on the command
line.
To achieve that, you have to activate the
spring.cloud.config.override-system-properties=false configuration
(the documentation only talk about System properties but it seems to
be applicable to command line arguments too).
I'm trying to inject "management.endpoints.web.base-path" into my class's field that I need to know. I did a few hours of search for it, but all the answer is "how to customize your endpoint" by setting management.endpoints.web.base-path in the application.xml(or yaml), not "how to get a default of management.endpoints.web.base-path".
Simple code as below was expecting to grab whatever variable loaded when Spring Boot app is starting up, but nothing was retrieved in the variable.
#Autowired
private Environment environment;
public void myMethod(){
final String actuatorBase = environment.getProperty("management.endpoints.web.base-path");
}
If I define it in the application.properties I should be able to load it for sure, but I'd like to know why the default("/actuator") can't be retrieved here. However when I run the application, I had no problem to access all the actuator related functionality endpoint through /actuator.
Since it doesn't work, injecting the variable with #Value annotation also doesn't work either.
When I checked the environment variable in the debugger, I was able to see application.yaml is loaded and all the overrides variables are there, too, but not all the default "management" stuff was there.
Any idea? This app has some custom configuration, not using all the AutoConfigurer stuff, so wondering if there is specific autoconfig I need to use to make it happen.
I'm using Spring Boot 2.1.0.RELEASE.
Self answering here.
WebEndpointProperties is the one that loads all the management.endpoints.web prefix properties, so simply
#Autowired
private WebEndpointProperties webEndpointProperties;
in the class and then
String actuatorWebBasePath = this.webEndpointProperties.getBasePath();
in my method gave me a base path(/actuator).
I am new to Spring Boot and I am doing code cleanup for my old Spring Boot application.
Below code is using #Value annotation to inject filed value from properties file.
#Value("${abc.local.configs.filepath}")
private String LOCAL_ABC_CONFIGS_XML_FILEPATH;
My doubt is instead of getting value from properties file, can we not directly hardcode the value in same java class variable?
Example: private String LOCAL_ABC_CONFIGS_XML_FILEPATH="/abc/config/abc.txt"
It would be easier for me to modify the values in future as it will be in same class.
What is advantage of reading from properties file, does it make the code decoupled ?
This technique is called as externalising configurations. You are absolutely right that you can have your constants defined in the very same class files. But, sometimes, your configurations are volatile or may change with respect to the environment being deployed to.
For Example:
Scene 1:
I have a variables for DB connection details which will change with the environment. Remember, you will create a build out of your application and deploy it first to Dev, then take same build to stage and finally to the production.
Having your configurations defined externally, helps you to pre-define them at environment level and have same build being deployed everywhere.
Scene 2:
You have already generated a build and deployed and found something was incorrect with the constants. Having those configurations externalised gives you a liberty to just override it on environment level and change without rebuilding your application.
To understand more about externalising techniques read: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
Here #value is used for reading the values from properties file (it could be a any environment like dev, qa, prod) but we are writing #value on multiple fields it is not recomonded so thats instead of #value we can use #configurableProperties(prefix="somevalue>) and read the property values suppose `
#configurableProperties(prefix="somevalue")
class Foo{
string name;
string address;
}
application.properties:
somevalue.name="your name"
somevalue.address="your address"
`
I'm building a module to save the data into the database. After finishing the module I will make it into a JAR which is common and anyone will call static method. There is a parameter its name application name i don’t want pass this value I want get this value dynamic after add jar to any spring boot application , then any one call this static method retrieve name application dynamic , so the spring boot contains the application properties have value spring.application.name I want get this value inside my module it’s doable ? it’s possible to get this value
I searched and found this implementation:
#Value("${spring.application.name}")
private String appName;
My class:
public class BackEnd {
#Value("${spring.application.name}")
static String applicationName;
private static void saveData(String messsage) {
DAO dao= new DAO()
dao.saveData(messsage,applicationName);
}
}
So currently the applicationName value is null. Is it the correct implementation?
The property name is correct and used by a handful of Spring Boot projects. You need to define this property yourself as Spring Boot default is an empty value as per docs:
# IDENTITY (ContextIdApplicationContextInitializer)
spring.application.name= # Application name.
You can use the usual application.yml file e.g.
spring:
application:
name: MyApp
well, the property name is correct but you need to define that property in application.properties or application.yml file as follows.
if you are using application.properties file define property as follows
spring.application.name= # your application name
if you are using application.yml file define property as follows
spring:
application:
name: # your application name
Background:
I have a Spring Boot 1.4 application running with Spring Cloud. My app is using the native profile to pull settings from an external config.properties file. The config server is embedded inside the same application.
In the config.properties file I have added the following setting:
app.setting=helloworld
What works:
When I change the property and send a REFRESH event, relevant beans marked are reloaded and the change is recognized correctly.
What doesn't work:
If I actually remove the property from config.properties, (by commenting it out for instance), the REFRESH event does nothing to actually refresh the application. Spring Cloud seems to not recognize the fact that the property is removed and when the data-binder proceeds to update the state of the world it misses the fact that the property is removed, and the corresponding bean linked to it must also be refreshed and its field set to blank/null, etc.
It looks like the data-binder only looks at what is at the moment available in the configuration and does not keep record of what was vs what is.
The only way to actually disable that setting in the bean configuration state is not by removing it, but by actually setting it to a blank value (which is a new value, given the setting is just a String). Note the field in Java bean mapped to this property has no default value other than null, and the value is not defined anywhere else (as in an embedded application.properties file, etc)
What might I be missing?
Is this a feature? Bug?
Thanks for your time.
Not sure if this is applicable to you, but I had a similar issue with beans annotated with #ConfigurationProperties and registered using #EnableAutoConfiguration:
#ConfigurationProperties(prefix="example")
#RefreshScope
public class MyConfig {
private List<String> values;
}
#EnableAutoConfiguration(MyConfig.class)
public class ApplicationConfiguration {
}
The problem I was experiencing is when you had a YAML configuration like:
example:
- Some
- Values
- Here
removing items from the list did not remove them from MyConfig.values when the context is refreshed.
The cause of this was that registering MyConfig using #EnableAutoConfiguration does not allow you to change the bean's scope, meaning that the bean does not get recreated when refreshing the context. See Github Issue.
My Fix
I removed MyConfig from #EnableAutoConfiguration and explicitly added a #Component annotation:
#Component
#ConfigurationProperties(prefix="example")
#RefreshScope
public class MyConfig {
private List<String> values;
}
#EnableAutoConfiguration
public class ApplicationConfiguration {
}
After this, removing items from the YAML list gets reflected in MyConfig when the context is refreshed.
I've run into a similar issue when refreshing an external config.properties file. The issue manifested itself with my config.properties because it only had a single entry in it.
To demonstrate:
Start by overriding the application properties
application.properties
some.value=abc
config.properties
some.value=xyz
Initially, using some.value will render "xyz"
To show that the value can be updated, change the value in the config.properties
config.properties
some.value=123
Using the /refresh endpoint, refresh the context and then using some.value will render "123"
Now, by removing the property, then we can see the value does not get updated
config.properties
// now empty
Using the /refresh endpoint, refresh the context and then using some.value will still render "123". It hadn't recognised that the field had been removed, nor used the "abc" value from the application.properties.
The issue stems from the class ConfigFileApplicationListener which on line 428, identifies the properties file as empty, so doesn't load the file into the property sources that are later used to compare the new properties to the old in the ContextRefresher class. The scenario appears to keep the old one in memory.
To workaround this issue, when you only have a single property, you could add property like a.b which would force the file to be loaded with the no value and result in the correct functionality.
config.properties
a.b=true
// item removed, but use some property to make sure it's read later
Hope this helps