Spring external configuration

Written by: Tom Spencer

Jul 09, 20225 min read

Spring External Configuration

I wanted to share a little experimentation I did with Spring externalized configuration. The idea came when looking at Wim Deblauwe's book Practical Guide to Building an API Back End with Spring Boot. In his book I was working on the REST API Security chapter and missed a properties value in the AuthorizationServerConfiguration class. The Spring docs lists 14 different ways to configure property values and I found this list a bit overwhelming!

spring docs

These options become available when you add @ConfigurationProperties to an existing class in your application. So I decided to look at some of the options for a small application to at least get some of the options under my fingers. The options I will show you in this article cover most realistic use cases when running applications in the wild. I looked at the following configuration options:

3. Config data (such as application.properties files)
5. OS environment variables
6. Java System properties (System.getProperties())
11. Command line arguments

The code for this article is available here.

First, I built a simple REST controller to print out the Configuration:

@RestController
public class ConfigController {
  private final EmailConfiguration emailConfiguration;

  public ConfigController(EmailConfiguration emailConfiguration) {
    this.emailConfiguration = emailConfiguration;
  }

  @GetMapping("/")
  public String propertiesConfig() {
    return emailConfiguration.toString();
  }
}

I added the EmailConfiguration class and autowired this class to the controller:

  @Component
  @ConfigurationProperties(prefix = "config-test.email")
  public class EmailConfiguration {

  @NotBlank
  private String serverName;

  @Email
  private String email;

  public String getServerName() {
    return serverName;
  }

  public void setServerName(String serverName) {
    this.serverName = serverName;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  @Override
  public String toString() {
    return "EmailConfiguration{" + "serverName='" + serverName + '\'' + ", email='" + email + '\'' + '}';
  }
}

I added a toString method on the EmailConfiguration class to show the values on the endpoint. I also added @Component and @ConfigurationProperties(prefix = "config-test.email"). The @Component creates an instance for the controller to autowire. The @ConfigurationProperties tells the configuration processor that the properties of the class should be made available for externalized configuration. The prefix annotation value gives a namespace for the properties so the values for the fields. We can then run mvn verify to package our application to a JAR file.

1. Application Properties

The values can then be set in the application.properties file like so:

config-test.email.email=tom@email.com
config-test.email.server-name=smtp.email.com

We then run our application like so:

java -jar target/config-test-0.0.1-SNAPSHOT.jar

And we see in our browser on localhost:8080: Screenshot 2022-07-09 at 12 52
34

This means we have successfully referred to our application.properties file to set values in our application. Yay!

2. Java system properties

We can also set values for the application to read as part of our jar command like so:

java -Dconfig-test.email.email=wim@wim-email.com -Dconfig-test.email.server-name=smtp.wim-email.com -jar
target/config-test-0.0.1-SNAPSHOT.jar

This allows us to set the variables as system properties and the values take precedence over the values set in our application.properties. We see the following on the browser on localhost:8080: Screenshot 2022-07-09 at 12 53
52

It works. Yay!

3. Command line arguments

We can also set the correct values as command line arguments when we run our jar file like so:

java -jar target/config-test-0.0.1-SNAPSHOT.jar --config-test.email.email=wim@wim-email.com
--config-test.email.server-name=smtp.wim-email.com

Again we see the correct values in the browser on localhost:8080: Screenshot 2022-07-09 at 12 53
52

Success!

4. OS Environment variables

Finally, we can set the values as environment variables on the machine that we run our application. We do this by setting the values as ENV variables on our Operating System. I am using fish shell so my configuration is slightly different. For normal bash profile we would use export.

set -x CONFIGTEST_EMAIL_SERVERNAME smtp.wim-email.com
set -x CONFIGTEST_EMAIL_EMAIL wim@wim-email.com

We can check we have set the environment variables correctly with printenv:

CONFIGTEST_EMAIL_EMAIL=wim@wim-email.com
CONFIGTEST_EMAIL_SERVERNAME=smtp.wim-email.com

Now when we run the jar command:

java -jar target/config-test-0.0.1-SNAPSHOT.jar

We also see the correct values on localhost:8080: Screenshot 2022-07-09 at 12 53
52

So there you have it, a short tour of setting values for running spring jar files with external configuration. Very useful when we want to set values from outside our application without having to recompile the whole application.

Extra

We can also add validation for our properties to ensure that we don't miss them out. If we commented out the application.properties values like so:

#config-test.email.email=tom@email.com
#config-test.email.server-name=smtp.email.com

and failed to set them on our jar command:

java -jar target/config-test-0.0.1-SNAPSHOT.jar

We get null values for our EmailConfiguration. This could cause a confusing error when running our application. Screenshot 2022-07-09 at 13 24 35

In order to fail at startup if properties are not set we can add validation to our EmailConfiguration class:



@Component
@ConfigurationProperties(prefix = "config-test.email")
@Validated
public class EmailConfiguration {

  @NotBlank
  private String serverName;

  @Email
  private String email;

  public String getServerName() {
    return serverName;
  }

  public void setServerName(String serverName) {
    this.serverName = serverName;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  @Override
  public String toString() {
    return "EmailConfiguration{" + "serverName='" + serverName + '\'' + ", email='" + email + '\'' + '}';
  }
}

We simply add @Validated to our class and @NotBlank and @Email to the serverName and email fields in our EmailConfiguration class. This means that we will get a validation error if we run mvn verify without having set the values in our application.properties file:


Binding validation errors on config-test.email
   - Field error in object 'config-test.email' on field 'serverName': rejected value [null]; codes [NotBlank.config-test.email.serverName,NotBlank.serverName,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [config-test.email.serverName,serverName]; arguments []; default message [serverName]]; default message [must not be blank]

We could also set the values as system properties from the command line:

mvn verify -Dconfig-test.email.email=wim@wim-email.com -Dconfig-test.email.server-name=smtp.wim-email.com