Issue : Configuration defined in application.properties is not overridden by environment variable.
I'm facing strange issue with spring configuration as configuration defined in application.properties is not overridden by environment variable when configuration is named in specific way. As mentioned in Externalized Configuration OS environment variables takes precedence over application.properties but this doesn't happen when configuration is defined as myExternal_url but it works when configuration is defined as my_external_url (in sample code below, we need to change configuration to my_external_url in ApplicationProperties.java and application.properties)
Sample Code -
@SpringBootApplication
public class ConfigApplication implements ApplicationRunner {
  @Autowired private ApplicationProperties applicationProperties;
  public static void main(String[] args) {
    SpringApplication.run(ConfigApplication.class, args);
  }
  @Override
  public void run(ApplicationArguments arg0) {
    System.out.println("External URL = " + applicationProperties.getMyExternalUrl());
  }
}
Application Bean configuration -
@Configuration
public class AppConfig {
  @Bean
  @ConfigurationProperties(prefix = "")
  public ApplicationProperties applicationProperties() {
    return new ApplicationProperties();
  }
}
ApplicationProperties class -
public class ApplicationProperties {
  @Value("${myExternal_url}")
  private String myExternalUrl;
  public String getMyExternalUrl() {
    return this.myExternalUrl;
  }
  public void setMyExternalUrl(String myExternalUrl) {
    this.myExternalUrl = myExternalUrl;
  }
}
application.properties:
myExternal_url=external_url_env_application_properties
What could be reason for this ?
EDIT - adding gradle Gradle configuration
plugins {
    id 'org.springframework.boot' version '2.4.0-M1'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compileOnly 'org.projectlombok:lombok:1.18.6'
    annotationProcessor 'org.projectlombok:lombok:1.18.6'
}
test {
    useJUnitPlatform()
}
EDIT 2
Trace log shows that myExternal_url is resolved correctly from Environment variable. Then Spring tries to resolve autowired dependencies 'applicationProperties' by calling AutowiredAnnotationBeanPostProcessor and then value is overridden by application.properties value (screen shot).
o.s.c.e.PropertySourcesPropertyResolver  : Found key 'myExternal_url' in PropertySource 'systemEnvironment' with value of type String
o.s.c.e.PropertySourcesPropertyResolver  : Found key 'myExternal_url' in PropertySource 'environmentProperties' with value of type String

TL;DR @Value has correct myExternal_Url from system variables injected, but its value is later set by @ConfigurationProperties.
The trace log is correct in that the ordering from Spring will place systemEnvironment before classpath:/application.properties in the propertySource list.
The issue you are having is because of a combination of using both @Value and @ConfigurationProperties to inject/bind your properties.
Say these are the values you provide:
system:
  myExternal_Url: foo
  my_external_url: bar
applications.properties:
  myExternal_url: aaa
In your ApplicationProperties:
  @Value("${myExternal_url}")
  private String myExternalUrl; // injected with foo
myExternalUrl is injected correctly with value (foo) you defined in your Environment Variables. However, @ConfigurationProperties binds the values after by using the setter methods. Since it uses relaxed bindings, it checks for different variations of myExternalUrl, it first looks for what's in your system variables and finds that myExternal_url(both camel and Underscore) isn't in one of relaxed binding forms , but then my_external_url (underscore only) is. So my_external_url's value is provided to setter:
public void setMyExternalUrl(String myExternalUrl) { // bar
  this.myExternalUrl = myExternalUrl; // myExternalUrl is reassigned from foo to bar
}
So it should be clear that your @Value would always be overridden since @ConfigurationProperties binds values after. Simply have:
public class ApplicationProperties {
    private String myExternalUrl;
    ...
then have one of binding forms - MY_EXTERNAL_URL, my-external-url, or my_external_url(Maybe there is more) - defined in your system. Then have consistent case in your application.yml in case you don't want your system variables.
my-external-url=aaa
Side Note. It is recommended that you use the form MY_EXTERNAL_URL as system environment variables.
I cannot reproduce your issue in 2.3.1.RELEASE and is working as expected and is overridden by my environment variable.
However I don't see the spring document where it says, @Value("${myExternal_url}") use relaxed binding as well. All I see is @ConfigurationProperties will use relaxed binding.
So I would update your class as follows (even through it works with and without in 2.3.1.RELEASE) @Value annotation removed
    public class ApplicationProperties {
      private String myExternalUrl;
      public String getMyExternalUrl() {
        return this.myExternalUrl;
      }
      public void setMyExternalUrl(String myExternalUrl) {
        this.myExternalUrl = myExternalUrl;
      }
    }  
Environment but when it is binding, it seems to decide if one overrides the other. You can see this by printing. Here I am getting the environment from applicationContext but you can autowire Environment and test this        System.out.println(context.getEnvironment()
                             .getProperty("myExternal_url"));
        System.out.println(context.getEnvironment()
                             .getProperty("my_external_url"));
@ConfigurationProperties support relaxed binding but not @Value. I created the following class   @Component
   public class ValueInjection {
    // This prints application properties.
    @Value("${myExternal_url}")
    private String myExternal_url;
    // This prints the environment variable
    @Value("${my_external_url}")
    private String my_external_url;
// If you uncomment this the application will not start saying 
// there is no such property.
// 
//    @Value("${myExternalUrl}")
//    private String myExternalUrl;
 //   ... getters and setters
    }
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With