Spring Boot: Configuring Properties

Introduction

Spring allows developers to configure a vast amount of properties for their projects. Spring Boot, besides allowing developers to start off with a project from scratch a lot more easily and time friendly than Spring, also makes it a lot easier to configure properties for your applications.

There are multiple ways to configure a Spring project:

  • Java-based
  • XML-based
  • Properties-based

Java and XML-based property configuration was a classic way of configuring Spring applications before Spring Boot introduced us with an application.properties file.

This addition allows us to externally configure the application and easily access properties defined in the file.

Injecting Properties

Application Setup

The easiest way to start with a skeleton project is to use Spring Initializr. Select your preferred version of Spring Boot, add the Web dependency and generate it as a Maven project:

Spring Initializr

If you open the project, you will notice that a file application.properties is being kept at the src/main/resources path.

This is the default file Spring relies upon to load the properties. We can write our custom or Spring-specific properties as key-value pairs here:

message.default.welcome=Welcome...
message.default.goodbye=Goodbye...

Instead of the properties file, we can also use a .yml file and define the same properties as:

message:
  default:
    welcome: Welcome...
    goodbye: Goodbye...

This works because of the SnakeYaml jar present in the classpath. YAML files are more concise and support maps, lists, etc.

It's up to you and your team which type to use. We will be using .properties type in this tutorial.

Injecting Properties Using @Value

Let's see how we can use these properties in a simple REST API:

@RestController
public class GreetController {

    @Value("${message.default.welcome}")
    private String welcomeMessage;

    @Value("${message.default.goodbye}")
    private String goodBye;

    @RequestMapping("/welcome")
    public String welcome() {
        return welcomeMessage;
    }

    @RequestMapping("/bye")
    public String bye() {
        return goodBye;
    }
}

This is pretty straightforward. Using the @Value annotation, we can inject the values from the application.properties file into class fields in the Spring-managed bean GreetController.

Then we have a couple of REST endpoints that simply return these values:

Result

Injecting Properties Using @ConfigurationProperties

If our properties have some common context like the same prefix, we can use the @ConfigurationProperties annotation which will map these properties to Java objects:

@Configuration
@ConfigurationProperties(prefix = "message.default")
public class MessageProperties {

    private String welcome;
    private String goodbye;

    // Getters and Setters
}
  • @Configuration will tell Spring to create a bean of this class.
  • @ConfigurationProperties will initialize the fields with corresponding property names.

We can use this bean in other Spring-managed beans like:

@Autowired
MessageProperties messageProperties;

Overriding Properties

Naturally, as our application environment expands and changes (development, QA, production, etc.), some of our properties will change accordingly.

This can be achieved by maintaining different files or getting the values of the properties through environment variables.

Using Spring Profiles

The most common way to write "changing" properties is to store them in different files. These files are environment-specific and our application can load them based on the environment variables.

Spring Boot provides a very elegant way to handle this.

All we have to do is follow a naming convention - application-<environment>.properties for our property files:

  • application-dev.properties
  • application-qa.properties
  • application-production.properties, etc

To notify Spring which files to use, we have to set an environment variable - spring.profiles.active.

So if the value of spring.profiles.active is dev, for an example, Spring boot will load the application-dev.properties file and likewise.

Note: application.properties is always loaded, irrespective of the spring.profiles.active value. If there is the same key-value present both in application.properties and application-<environment>.properties, the latter will override the former.

Typically we write all the common properties of every environment in application.properties and override environment-specific properties using the profile-specific application-<environment>.properties.

Let's see this by creating a application-dev.properties:

message.default.welcome = Welcome to DEV environment...

There are few ways to set up spring.profiles.active variable.

If we are running the application through Eclipse, we can set this in VM arguments:

Eclipse VM Arguments

We can set it in the OS environment variables, like in Windows:

windows env

Let's start our application and in the logs, you can see the dev profile being loaded:

spring profiles active dev logs

Lets's check both of our previous REST endpoints:

spring profiles active dev

As we can see, message.default.welcome value came from application-dev.properties file and message.default.goodbye property came from application.properties.

We can have multiple values in spring.profiles.active like dev,qa:

spring profiles active dev/qa

Any duplicate key would be overridden by the last profile, in the above case being qa.

We can also pass in the spring.profiles.active as a command line argument like:

java -jar -Dspring.profiles.active=dev greeting-service-0.0.1-SNAPSHOT.jar

Creating application.properties from Build Location

We can also override the internal properties by creating an application.properties file at the same level from where the .jar is executed. Spring context will override properties using this newly created file.

This is a great way of distributing your application to others, who can override certain properties based on their environment, like database configurations for an example.

There are other ways to externalize your properties too, like as OS environment variables, command line arguments, etc. The order in which Spring considers it can be found here.

Externalizing Properties Using Cloud Configuration Server

Many of the applications built nowadays rely on the microservice architecture. Not only are these applications deployed separately but they could have multiple instances of themselves (based on load) and the total count could easily go above 100.

Managing properties in this particular architecture style via conventional methods requires too much effort. Also, to change a property, we have to re-build the application again and deploy it or at best have to restart the application. This requires downtime, which sort of defeats the whole purpose of microservices.

Another problem with the traditional approach, especially if the properties were externalized via file or environment variables is that there's no traceability. The latest ones are always taken and we don't know what the properties were before or who changed it.

Spring Cloud Config provides a centralized, externalized, secure and easy way for storing and serving configurations for applications for different environments:

spring config server diagram

In short, we have a Config Server running as a separate application that hooks to a Git repository.

When we start up a new application (Config Client), it gets all the needed properties from the Config Server. It doesn't matter if the application existed when we set the server up or not.

Creating a Config Server

As always, we start by using Spring Initializr.

Select your preferred version of Spring Boot, add the Config Server dependency and generate it as a Maven project:

spring config server

By annotating our main class with @EnableConfigServer, we mark it as being a config server:

@SpringBootApplication
@EnableConfigServer
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

And now, we have to set up a few things in the application.properties file:

server.port = 8888
spring.cloud.config.server.git.uri = https://github.com/dhananjay12/spring-cloud-config
spring.cloud.config.server.git.searchPaths = app-properties

Here, we defined the port on which config server would be running. Then we specified the Git URL that it needs to hook for properties.

Note: By default, Spring searches for the property files at the root. If we have to specify a particular folder we can provide the location via searchPaths.

Here's how the Git repo looks like:

greeting service cloud

We can start the config server now. If you'd like to check the configuration of the Spring Config Server, following the convention - http://localhost:8888/<application-name>/<spring-profiles> will show us all the needed information.

In our case, it would be - http://localhost:8888/greeting-service-cloud/default:

greeting service cloud

Creating a Config Client

Let's create the same greeting service but with a couple of extra dependencies:

greeting service cloud

Here, we created greeting-service-cloud service with Web, Config Client and Actuator dependencies.

It has the same REST mappings as before, with the addition of @RefreshScope annotation. This annotation allows the bean to be refreshed dynamically at runtime:

@RestController
@RefreshScope
public class GreetController {

    @Value("${message.default.welcome}")
    private String welcomeMessage;

    @Value("${message.default.goodbye}")
    private String goodBye;

    @RequestMapping("/welcome")
    public String welcome() {
        return welcomeMessage;
    }
    @RequestMapping("/bye")
    public String bye() {
        return goodBye;
    }
}

Apart from application.properties we now have to create bootstrap.properties, which is loaded before the application.properties.

It is typically used by the Spring Config Client to get properties from the Spring Config Server:

spring.application.name = greeting-service-cloud
spring.cloud.config.uri = http://localhost:8888

Here, we first set the application name. The Spring Config Server will search for this file name in the Git repository and serve its contents.

We also have to mention where the Config Server is running by specifying it in spring.cloud.config.uri.

Let's start this service and take a look at the logs:

greeting service cloud logs

Notice that it first got the properties from the Spring Config Server.

Note: If the Config Server is not reachable, the application won't start.

Now let's check our REST endpoints:

greeting service cloud apis

So we externalized our properties and have nice traceability of it in our Git repository. Some important points worth noting:

  • We can use spring-profiles-active here too. If this variable is set in the Config Client environment for eg. dev, it will be passed to the Config Server while requesting properties. The config server will then look for greeting-service-cloud-dev.properties in the Git repository and serve it to the client.
  • If there is a application.properties present in the Git repository, it will be served to all the clients in addition to other files.
  • If the Config Client requests properties, for example, say dev profile, the Config Server will return application.properties, application-dev.properties and greeting-service-cloud-dev.properties. The common properties will be overridden by the last one.

Refreshing Properties without Restart

By default, the configuration values from properties files are ready or fetched at the application startup and not again. If there are some changes to be made, we still have to restart the application.

To solve this we added the Actuator dependency to our application. It provides some production ready endpoints that can give insights regarding our application which can be used for administrative purposes.

We have to enable these endpoints manually by specifying management.endpoints.web.exposure.include = * in the application properties.

Let's add this to the Git repo and restart the application. We can check many details of our application by visiting endpoints like http://localhost:8080/actuator/env, http://localhost:8080/actuator/mappings, etc.

The one we are interested in is the /actuator/refresh. We can force a bean to refresh its configuration (i.e, to pull configuration again from the config server) by annotating the bean with @RefreshScope.

Note: If a bean is refreshed then the next time the bean is accessed (i.e. a method is executed) a new instance is created.

This can be triggered by sending an empty HTTP POST request to the client’s refresh endpoint - http://<host:port>/actuator/refresh.

Let's change the value of one in the Git repository to something else:

message.default.welcome = Welcome from cloud config server changed...
message.default.goodbye = Goodbye...

management.endpoints.web.exposure.include = *

Now lets trigger the refresh endpoint:

curl localhost:8080/actuator/refresh -d {} -H "Content-Type: application/json"

Check the /welcome endpoint:

greeting service cloud refresh

So we were able to refresh the property of a running application without restarting it.

Conclusion

In this article, we've covered how to configure properties in our Spring Boot application.

We first discussed simple ways to inject properties to our application and then changing/overriding these properties based on different environments.

Afterwards, we covered how to get properties from Spring Config Server and how to update properties without a rebuild or restart.

As always, the code for the examples used in this article can be found on Github.