This guide walks you through the process of standing up and consuming configuration from the Spring Cloud Config Server

What You Will Build

You will set up a Config Server and build a client that consumes the configuration on startup and then refreshes the configuration without restarting the client.

What You need

How to complete this guide

Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

To start from scratch, move on to [scratch].

To skip the basics, do the following:

When you finish, you can check your results against the code in gs-centralized-configuration/complete.

Starting with Spring Initializr

For all Spring applications, you should start with the Spring Initializr. The Initializr offers a fast way to pull in all the dependencies you need for an application and does a lot of the set up for you.

This guide needs two applications. The first application (the configuration service) needs only the Config Server dependency. The following image shows the Initializr set up for the configuration service:

initializr service
The preceding image shows the Initializr with Maven chosen as the build tool. You can also use Gradle. It also shows values of com.example and centralized-configuration-service as the Group and Artifact, respectively. You will use those values throughout the rest of this sample.

The following listing shows the pom.xml file (for the configuration service) that was created when you choose Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>centralized-configuration-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>centralized-configuration-service</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-server</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

The following listing shows the build.gradle file (for the configuration service) that was created when you choose Gradle:

plugins {
	id 'org.springframework.boot' version '2.1.8.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

ext {
	set('springCloudVersion', "Greenwich.SR3")
}

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-config-server'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

The second application (the configuration client) needs the Config Client, Spring Boot Actuator, and Spring Web dependencies. The following image shows the Initializr set up for the configuration client:

initializr client
The preceding image shows the Initializr with Maven chosen as the build tool. You can also use Gradle. It also shows values of com.example and centralized-configuration-client as the Group and Artifact, respectively. You will use those values throughout the rest of this sample.

The following listing shows the pom.xml file (for the configuration client) that was created when you choose Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>centralized-configuration-client</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>centralized-configuration-client</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

The following listing shows the build.gradle file (for the configuration client) that was created when you choose Gradle:

buildscript {
    ext { springBootVersion = '2.1.6.RELEASE' }
    repositories { mavenCentral() }
    dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
    baseName = 'configuration-client'
    version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories { mavenCentral() }

dependencies {
    compile('org.springframework.cloud:spring-cloud-starter-config')
    compile('org.springframework.boot:spring-boot-starter-actuator')
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2"
    }
}

eclipse {
    classpath {
        containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
        containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
    }
}
For convenience, we have provided build files (a pom.xml file and a build.gradle file) at the top of the project (one directory above the client and service directories) that you can use to build both projects at once. We also added the Maven and Gradle wrappers there.

Stand up a Config Server

You first need a Config Service to act as a sort of intermediary between your Spring applications and a (typically) version-controlled repository of configuration files. You can use Spring Cloud’s @EnableConfigServer to standup a config server that can communicate with other applications. This is a regular Spring Boot application with one annotation added to enable the config server. The following listing (from configuration-service/src/main/java/com/example/configurationservice/ConfigurationServiceApplication.java) shows such an application:

package com.example.configurationservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableConfigServer
@SpringBootApplication
public class ConfigurationServiceApplication {

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

The Config Server needs to know which repository to manage. There are several choices here, but start with a Git-based filesystem repository. You could as easily point the Config Server to a Github or GitLab repository. On the file system, create a new directory and run git init in it. Then add a file called a-bootiful-client.properties to the Git repository. Then run git commit in it. Later, you will connect to the Config Server with a Spring Boot application whose spring.application.name property identifies it as a-bootiful-client to the Config Server. This is how the Config Server knows which set of configuration to send to a specific client. It also sends all the values from any file named application.properties or application.yml in the Git repository. Property keys in more specifically named files (such as a-bootiful-client.properties) override those in application.properties or application.yml.

Add a simple property and value (message = Hello world) to the newly created a-bootiful-client.properties file and then git commit the change.

Specify the path to the Git repository by specifying the spring.cloud.config.server.git.uri property in configuration-service/src/main/resources/application.properties. You must also specify a different server.port value to avoid port conflicts when you run both this server and another Spring Boot application on the same machine. The following listing (from configuration-service/src/main/resources/application.properties) shows such an application.properties file:

server.port=8888

spring.cloud.config.server.git.uri=${HOME}/Desktop/config

This example uses a file-based git repository at ${HOME}/Desktop/config. You can create one easily by making a new directory and running git commit on the properties and YAML files in it. The following set of commands does this work:

$ cd ~/Desktop/config
$ find .
./.git
...
./application.yml

Or you could use a remote git repository (such as Github) if you change the configuration file in the application to point to that instead.

Reading Configuration from the Config Server by Using the Config Client

Now that you have stood up a Config Server, you need to stand up a new Spring Boot application that uses the Config Server to load its own configuration and that refreshes its configuration to reflect changes to the Config Server on-demand, without restarting the JVM. To do so, add the org.springframework.cloud:spring-cloud-starter-config dependency, to connect to the Config Server. Spring sees the configuration property files, as it would any property file loaded from application.properties or application.yml or any other PropertySource.

The properties to configure the Config Client must necessarily be read in before the rest of the application’s configuration is read from the Config Server, during the bootstrap phase. Specify the client’s spring.application.name as a-bootiful-client and the location of the Config Server (spring.cloud.config.uri) in configuration-client/src/main/resources/bootstrap.properties, where it will be loaded earlier than any other configuration. The following listing shows that file:

configuration-client/src/main/resources/bootstrap.properties

spring.application.name=a-bootiful-client
# N.B. this is the default:
spring.cloud.config.uri=http://localhost:8888

You also want to enable the /refresh endpoint, to demonstrate dynamic configuration changes. The following listing (from configuration-client/src/main/resources/application.properties) shows how to do so:

management.endpoints.web.exposure.include=*

The client can access any value in the Config Server by using the traditional mechanisms (such as @ConfigurationProperties or @Value("${…​}") or through the Environment abstraction). Now you need to create a Spring MVC REST controller that returns the resolved message property’s value. See the Building a RESTful Web Service guide to learn more about building REST services with Spring MVC and Spring Boot.

By default, the configuration values are read on the client’s startup and not again. You can force a bean to refresh its configuration (that is, to pull updated values from the Config Server) by annotating the MessageRestController with the Spring Cloud Config @RefreshScope and then triggering a refresh event. The following listing (from configuration-client/src/main/java/com/example/configurationclient/ConfigurationClientApplication.java) shows how to do so:

package com.example.configurationclient;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class ConfigurationClientApplication {

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

@RefreshScope
@RestController
class MessageRestController {

    @Value("${message:Hello default}")
    private String message;

    @RequestMapping("/message")
    String getMessage() {
        return this.message;
    }
}

Test the Application

You can test the end-to-end result by starting the Config Service first and then, once it is running, starting the client. Visit the client app in the browser at http://localhost:8080/message. There, you should see Hello world in the response.

Change the message key in the a-bootiful-client.properties file in the Git repository to something different (Hello Spring!, perhaps?). You can confirm that the Config Server sees the change by visiting http://localhost:8888/a-bootiful-client/default. You need to invoke the refresh Spring Boot Actuator endpoint in order to force the client to refresh itself and draw in the new value. Spring Boot’s Actuator exposes operational endpoints (such as health checks and environment information) about an application. To use it, you must add org.springframework.boot:spring-boot-starter-actuator to the client application’s classpath. You can invoke the refresh Actuator endpoint by sending an empty HTTP POST to the client’s refresh endpoint: http://localhost:8080/actuator/refresh. Then you can confirm it worked by visting the http://localhost:8080/message endpoint.

The following command invokes the Actuator’s refresh command:

$ curl localhost:8080/actuator/refresh -d {} -H "Content-Type: application/json"
We set management.endpoints.web.exposure.include=* in the client application to make this is easy to test (since Spring Boot 2.0, the Actuator endpoints are not exposed by default). By default, you can still access them over JMX if you do not set the flag.

Summary

Congratulations! You have just used Spring to centralize configuration for all of your services by first standing up a service and then dynamically updating its configuration.

See Also

The following guides may also be helpful:

Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.

All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing.