
Introduction
Configuring a software system in a monolithic approach does not pose particular problems. To make the configuration properties available to the system, we can store them in a file inside an application folder, in some place in the filesystem, or as OS environment variables. Microservice Configuration is a more complex subject. We have to deal with a number, which can be huge, of totally independent services, each with its own configuration. We could even face a scenario in which several instances of the same service need different configuration values.
In such a situation, a way to centralize and simplify configuration management would be of great importance. Spring Cloud has its own module to solve these problems, named Spring Cloud Config. This module provides an implementation of a server that exposes an API to retrieve the configuration information, usually stored in some remote repository like Git, and at the same time, it gives us the means to implement the client side meant to consume the services of that API.
In the first part of this article, we will discuss the basic features of this Spring Cloud module, storing the configuration in the configuration server classpath, and in the next part, we will show how to use other, more effective, repository options, like Git, and also how to refresh the configuration without restarting the services. Then, in later posts, we will show how the centralized configuration can be coupled with service discovery features to set a solid base for the whole microservice system.
Microservice Configuration: Spring Cloud Config Server Side
The first component we need in a distributed configuration scenario is a server meant to provide the configuration information for the services. To implement such a server component by Spring Cloud Config we have to use the right Spring Boot “starter” dependency, like in the following configuration fragment:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
Then we have to annotate the Spring Boot main class with the @EnableConfigServer annotation:
@SpringBootApplication
@EnableConfigServer
public class AppMain {
public static void main(String[] args) {
new SpringApplicationBuilder(Config.class).run(args);
}
}
The Spring Cloud Config server, according to the auto-configuration features of Spring Boot, would run on the default 8080 port as all Spring Boot applications. If we want to customize it, we can do it by the application.properties or application.yaml file:
server:
port: ${PORT:8888}
spring:
application:
name: config-server
If we run the application with the above configuration it will use the 8888 port as default. We can override the default by launching the application with a different port, by the PORT placeholder:
java -jar -DPORT=8889 sample-server-1.0-SNAPSHOT.jar
In any case, If we launch the application with the spring.config.name=configserver argument instead, the default port will be 8888. This is due to a configserver.yml default file embedded in the spring-cloud-config-server library. As a matter of fact, it would be very convenient to launch the server config application on the 8888 port, either by explicitly configuring it by the server.port parameter, like in the example above, or passing spring.config.name=configserver in the startup Java command, because 8888 happens to be the default port used by the client side.
Important note: the spring.config.name=configserver option only works if passed in the startup command and seems to be ignored for some reason if set in the configuration file. We can see below an example of how to start the config server with a Java command:
java -jar -Dspring.config.name=configserver spring-cloud-config-native-server-1.0-SNAPSHOT.jar
By default, the Spring Cloud Config server uses Git as a remote repository to store the configuration data. In this article, to simplify the discussion we will focus on a more basic approach based on files stored on the application classpath. We will describe this option in the next section. It must be stressed that in a real scenario, this would be far from ideal and Git would be surely a better choice.
Enforcing Basic Authentication on the Server Side
We can provide the server with a basic security layer, in the form of an authentication mechanism based on user and password. To do that we must first add the following security starter to the POM:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
And then add the following piece of configuration in the application.yml file:
security:
user:
name: myusername
password: mypassword
With the above, the client side should be configured accordingly to be able to connect to the server, as we will see in the section related to the client side. We will discuss more advanced securing mechanisms in later articles.
Spring Cloud Config Backend Storing Options
The Spring Cloud Config server can store the configuration data in several ways:
- By a remote Git system. This is the default.
- Other Version Control Systems (VCS) like SVN.
- VAULT: it is a tool by HashiCorp specialized in storing passwords, certificates, or other entities as secrets.
- By storing it in some place in the filesystem or in the classpath.
In this article, we will describe the filesystem/classpath option. Spring Cloud Config has a profile named native that covers this scenario. In order to run the config server with a filesystem/classpath backend storage we have to start it with the spring.profiles.active=native option.
In the native scenario, the config server will search by default in the following places:
- classpath:/
- classpath:/config
- file:./
- file:./ config
So, we can simply store the configuration files inside the application jar file. If we want to use an external filesystem directory instead or customize the above classpath options, we can set the spring.cloud.config.server.native.searchLocations property accordingly.
Config Server API
The Config Server can expose the configuration properties of a specific application by an HTTP API with the following endpoints:
- /{ application}/{ profile}[/{ label}]: this returns the configuration data as JSON, with the specific application, profile, and an optional label parameter.
- /{ label}/{ application}-{ profile}.yml: this returns the configuration data in YAML format, with the specific application, profile, and an optional label parameter.
- /{ label}/{ application}-{ profile}. properties: this returns the configuration data as raw text, with the specific application, profile, and an optional label parameter.
The application part represents the name of the application, configured by the spring.application.name property and the profile part represents the active profile. A profile is a feature to segregate a set of configurations related to specific environments, such as development, test, and production. The label part is optional and is used when using Git as a backend repository to identify a specific branch.
Microservice Configuration: Spring Cloud Config Client Side
If we want our services to obtain their own configuration from the server we must provide them with a dependency named spring-cloud-starter-config:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
Clearly, the configuration must be obtained as the first step during its startup. To deal with this requirement, Spring Cloud introduces a bootstrap context. The bootstrap context can be seen as the parent of the application context. It serves the purpose of loading configuration retrieved data from some external source and making it available to the application context.
In earlier versions of Spring Cloud, we could provide the configuration properties for the bootstrap context by a bootstrap.yml file. This is deprecated in the new versions. Now, we have to simply provide a config.import property=optional:configserver: property in the standard application.yml:
config:
import: "optional:configserver:"
With the “optional:configserver:” value the config client service will use the default http://localhost:8888 address to contact the config server. If we exclude the optional part an error will be raised during startup, if the server is unreachable.
If we want to set a specific address and port, we can add the address part to the value, like this:
config:
import: "optional:configserver:http://myhost:myport"
Configuring Security on the Client Side
If we have secured the server with basic authentication we must provide the necessary configuration to the client. Adding the following piece of configuration to the application.yml will be enough:
security:
user:
name: myusername
password: mypassword
Putting the Pieces Together in a Simple Demo
Using the notions described above we can realize a simple demo with a configuration server and a single client service, as in the picture below:

Server Side Implementation
To implement the server side we create a Spring Boot application with the required Spring Cloud release train and Spring Cloud Config starter:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
Then we write the application.yml file, setting the port to the conventional 8888 value, the active profile as native, and finally the application name:
server:
port: ${PORT:8888}
spring:
profiles:
active: native
application:
name: config-server
Since we have set the spring.profiles.active equal to the native value, this config server application storage will be based on filesystem/classpath. In our example, we choose to store the configuration file of the client service in the classpath, in a config subdirectory of the /resources folder. We name the client service application file name as client-service.yml and we fill it with the following content:
server:
port: ${PORT:8081}
myproperty: value
myproperties:
properties:
- value1
- value2
The myproperty and myproperties parts will be used to test this minimal demo: we will expose them by some REST service on the client and if all works as expected the above values will be returned.
Client Side Implementation
We configure the client application with the same release train of the server. As dependencies with have a spring-cloud-starter-config starter, and also a spring-boot-starter-web, because we want our application to expose some HTTP REST services:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
The application.yml properties will be consumed by a specific component class by the @Value and @ConfigurationProperties annotations:
@Component
@ConfigurationProperties(prefix = "myproperties")
public class DemoClient {
private List<String> properties = new ArrayList<String>();
public List<String> getProperties() {
return properties;
}
@Value("${myproperty}")
private String myproperty;
public String getMyproperty() {
return myproperty;
}
}
Then a controller class will implement two REST services, /getProperties and /getProperty, returning the above class properties:
@RestController
public class ConfigClientController {
private static final Logger LOG = LoggerFactory.getLogger(ConfigClientController.class);
@Autowired
private DemoClient demoClient;
@GetMapping("/getProperties")
public List<String> getProperties() {
LOG.info("Properties: " + demoClient.getProperties().toString());
return demoClient.getProperties();
}
@GetMapping("/getProperty")
public String getProperty() {
LOG.info("Property: " + demoClient.getMyproperty().toString());
return demoClient.getMyproperty();
}
}
Compiling and running the Config Server and the Client Service
After compiling the two applications by Maven, we can take the resulting jars and run first the server part and then the client from the command line:
java -jar spring-cloud-config-native-server-1.0-SNAPSHOT.jar
...
java -jar spring-cloud-config-native-client-1.0-SNAPSHOT.jar
We can test the correct behavior by executing a call with the following address in the browser:
http://localhost:8081/getProperties
If all works as expected we will have the following values printed on the screen:
[
"value1",
"value2"
]
The Maven projects related to the demo described above are available on GitHub at the following addresses:
Conclusion
In this article, we have covered the basic notions required to configure a microservice system based on remote configuration. We have used here the native approach, using the classpath as a storage repository. In the next part we will show how to use a remote Git repository, and also how to refresh the configuration at runtime without restarting the services.