Spring Cloud Config Server in Java

Spring Cloud Config Server provides centralized external configuration management distributed across multiple applications and environments. This comprehensive guide covers setting up, configuring, and using Spring Cloud Config Server in Java applications.

Basic Config Server Setup

1. Config Server Dependencies

Maven Configuration:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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>
</project>

2. Basic Config Server Application

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

3. Config Server Configuration

application.yml:

server:
port: 8888
spring:
application:
name: config-server
profiles:
active: native, git
security:
user:
name: config-user
password: config-password
cloud:
config:
server:
# Git backend configuration
git:
uri: https://github.com/my-organization/config-repo
default-label: main
search-paths: 
- "applications/{application}"
- "environments/{profile}"
timeout: 5
force-pull: true
# Native file system backend
native:
search-locations: 
- "classpath:/config/"
- "file:./config/"
- "file:/etc/config/"
# Composite configuration with multiple backends
composite:
- type: git
uri: https://github.com/my-org/config-repo
- type: vault
host: 127.0.0.1
port: 8200
kv-version: 2
# Encryption configuration
encrypt:
key-store:
location: classpath:keystore.jks
password: storepass
alias: configkey
secret: keypass
# Management endpoints
management:
endpoints:
web:
exposure:
include: health,info,metrics,refresh,bus-refresh
endpoint:
health:
show-details: always

Git Backend Configuration

4. Advanced Git Configuration

spring:
cloud:
config:
server:
git:
uri: https://github.com/my-organization/config-repo
default-label: main
search-paths: 
- "applications/{application}"
- "services/{application}"
- "shared/{profile}"
repos:
# Application-specific repositories
my-special-app:
pattern: my-special-app*
uri: https://github.com/my-organization/special-config-repo
search-paths: 
- "config"
# Development team repositories
team-a:
pattern: team-a-*
uri: https://github.com/team-a/config-repo
team-b:
pattern: team-b-*
uri: https://github.com/team-b/config-repo
# Git authentication
username: ${GIT_USERNAME}
password: ${GIT_PASSWORD}
passphrase: ${GIT_PASSPHRASE}
private-key: ${GIT_PRIVATE_KEY}
# Git configuration
timeout: 10
basedir: /tmp/config-repo
force-pull: true
delete-untracked-branches: true
clone-on-start: true

5. File System Backend

spring:
profiles:
active: native
cloud:
config:
server:
native:
search-locations:
- "classpath:/config/"
- "file:./config-repo/"
- "file:/etc/application-config/"
- "optional:configserver:${user.home}/config-repo/"

Security Configuration

6. Security Configurations

@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/actuator/info").permitAll()
.anyRequest().authenticated()
)
.httpBasic(withDefaults())
.csrf(csrf -> csrf.ignoringRequestMatchers("/actuator/bus-refresh"));
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("config-user")
.password("{bcrypt}$2a$10$dXJ3SW6G7P.XBLBvanJYc.ZO.8bS5ZL.8L.8.8.8.8.8.8.8.8.8.8.8")
.roles("CONFIG")
.build();
return new InMemoryUserDetailsManager(user);
}
}

7. JWT Security with Config Server

@Configuration
public class JwtSecurityConfig {
@Value("${jwt.secret:defaultSecret}")
private String jwtSecret;
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withSecretKey(
new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256")
).build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}

Client Configuration

8. Config Client Setup

Client Dependencies:

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

bootstrap.yml (Client):

spring:
application:
name: my-client-app
profiles:
active: dev
cloud:
config:
uri: http://localhost:8888
username: config-user
password: config-password
fail-fast: true
retry:
initial-interval: 1000
max-interval: 2000
max-attempts: 6
multiplier: 1.1
request-connect-timeout: 5000
request-read-timeout: 5000
# Label for Git branch/tag
label: main
# Profile-specific configuration
profile: dev,eu-west
# Discovery first approach
discovery:
enabled: true
service-id: config-server
# Enable refresh endpoint
management:
endpoints:
web:
exposure:
include: health,info,refresh
endpoint:
refresh:
enabled: true

9. Client Application with Refresh Scope

@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${app.message:Hello default}")
private String message;
@Value("${app.feature.enabled:false}")
private boolean featureEnabled;
@Autowired
private AppConfig appConfig;
@GetMapping("/message")
public String getMessage() {
return message;
}
@GetMapping("/config")
public AppConfig getConfig() {
return appConfig;
}
@PostMapping("/refresh")
public void refresh() {
// Configuration will be refreshed automatically
}
}
@Component
@ConfigurationProperties(prefix = "app")
@RefreshScope
public class AppConfig {
private String name;
private int version;
private DatabaseConfig database;
private List<String> servers = new ArrayList<>();
public static class DatabaseConfig {
private String url;
private String username;
private String password;
private int poolSize;
// Getters and setters
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getPoolSize() { return poolSize; }
public void setPoolSize(int poolSize) { this.poolSize = poolSize; }
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getVersion() { return version; }
public void setVersion(int version) { this.version = version; }
public DatabaseConfig getDatabase() { return database; }
public void setDatabase(DatabaseConfig database) { this.database = database; }
public List<String> getServers() { return servers; }
public void setServers(List<String> servers) { this.servers = servers; }
}

Configuration Files Structure

10. Git Repository Structure

config-repo/
├── applications/
│   ├── my-client-app/
│   │   ├── application.yml
│   │   ├── application-dev.yml
│   │   ├── application-prod.yml
│   │   └── application-uat.yml
│   └── another-app/
│       ├── application.yml
│       └── application-prod.yml
├── shared/
│   ├── application-common.yml
│   ├── database-common.yml
│   └── security-common.yml
└── environments/
├── dev/
│   └── application.yml
├── prod/
│   └── application.yml
└── uat/
└── application.yml

11. Configuration File Examples

application.yml (Shared):

# Common configuration for all applications
app:
version: 1.0.0
company: "My Company"
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.cloud: INFO
management:
endpoints:
web:
exposure:
include: health,info,metrics,refresh
endpoint:
health:
show-details: always

my-client-app.yml:

app:
name: "My Client Application"
message: "Hello from Config Server"
feature:
enabled: true
name: "New Dashboard"
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: ${DB_USERNAME:appuser}
password: ${DB_PASSWORD:apppass}
hikari:
maximum-pool-size: 20
minimum-idle: 5
jpa:
hibernate:
ddl-auto: validate
show-sql: false
custom:
cache:
ttl: 300s
max-size: 1000
retry:
max-attempts: 3
backoff: 1000ms

my-client-app-dev.yml:

app:
message: "Hello from Development Environment"
feature:
enabled: true
spring:
datasource:
url: jdbc:h2:mem:testdb
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop
logging:
level:
com.example: DEBUG

my-client-app-prod.yml:

app:
message: "Hello from Production Environment"
feature:
enabled: false
spring:
datasource:
url: jdbc:mysql://prod-db:3306/mydb
jpa:
show-sql: false
hibernate:
ddl-auto: validate
management:
endpoints:
web:
exposure:
include: health,info

Encryption and Decryption

12. Encryption Setup

@Configuration
public class EncryptionConfig {
@Bean
public TextEncryptor textEncryptor(
@Value("${encrypt.key:defaultKey}") String key) {
return Encryptors.text(key, "deadbeef");
}
@Bean
@ConditionalOnProperty(name = "encrypt.key-store.location")
public TextEncryptor keyStoreTextEncryptor(
@Value("${encrypt.key-store.location}") Resource location,
@Value("${encrypt.key-store.password}") String storePass,
@Value("${encrypt.key-store.alias}") String alias,
@Value("${encrypt.key-store.secret}") String secret) {
return Encryptors.text(storePass, secret);
}
}

Using Encryption:

# Encrypted values in configuration
spring:
datasource:
password: '{cipher}FKSAJDFGYOS8F7GHAKJFGHLKERHG0943Y598HKLJDFHGLKJDHFGLKJDHFG'
app:
api-key: '{cipher}AQICAHh...long-encrypted-string...'

13. Encryption Endpoints

@RestController
@RequestMapping("/encrypt")
public class EncryptionController {
@Autowired
private TextEncryptor textEncryptor;
@PostMapping
public String encrypt(@RequestBody String plainText) {
return textEncryptor.encrypt(plainText);
}
@PostMapping("/decrypt")
public String decrypt(@RequestBody String encryptedText) {
return textEncryptor.decrypt(encryptedText);
}
}

Service Discovery Integration

14. Config Server with Eureka

# Config Server with Service Discovery
spring:
cloud:
config:
server:
git:
uri: https://github.com/my-org/config-repo
discovery:
enabled: true
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
# Client configuration with discovery
spring:
cloud:
config:
discovery:
enabled: true
service-id: config-server
fail-fast: true

Monitoring and Management

15. Health Check and Monitoring

@Component
public class ConfigServerHealthIndicator implements HealthIndicator {
@Autowired
private EnvironmentRepository environmentRepository;
@Override
public Health health() {
try {
// Test configuration retrieval
environmentRepository.findOne("config-server", "default", null);
return Health.up()
.withDetail("repository", "accessible")
.build();
} catch (Exception e) {
return Health.down(e)
.withDetail("repository", "unreachable")
.build();
}
}
}

Custom Actuator Endpoints:

@RestController
@RequestMapping("/actuator/config")
public class ConfigActuatorEndpoint {
@Autowired
private EnvironmentRepository environmentRepository;
@GetMapping("/applications")
public List<String> getApplications() {
// Return list of configured applications
return Arrays.asList("app1", "app2", "app3");
}
@GetMapping("/{application}/{profile}")
public Map<String, Object> getConfiguration(
@PathVariable String application,
@PathVariable String profile) {
Environment env = environmentRepository.findOne(application, profile, null);
Map<String, Object> config = new HashMap<>();
config.put("name", env.getName());
config.put("profiles", env.getProfiles());
config.put("propertySources", env.getPropertySources());
return config;
}
}

High Availability Setup

16. Config Server Cluster

# Multiple Config Server instances with load balancing
spring:
cloud:
config:
server:
git:
uri: https://github.com/my-org/config-repo
# Client configuration for multiple servers
uri: 
- http://config-server-1:8888
- http://config-server-2:8888
- http://config-server-3:8888
# With service discovery
eureka:
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-2:8761/eureka/

Testing Configuration

17. Test Configurations

@SpringBootTest
@ActiveProfiles("test")
public class ConfigServerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testConfigServerEndpoints() {
ResponseEntity<String> response = restTemplate
.withBasicAuth("config-user", "config-password")
.getForEntity("/my-client-app/default", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void testEncryption() {
String plainText = "secret-value";
ResponseEntity<String> encrypted = restTemplate
.postForEntity("/encrypt", plainText, String.class);
assertThat(encrypted.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
@SpringBootTest
public class ConfigClientTest {
@Autowired
private AppConfig appConfig;
@Test
public void testConfigurationProperties() {
assertThat(appConfig.getName()).isEqualTo("My Client Application");
assertThat(appConfig.getVersion()).isEqualTo(1);
}
@TestConfiguration
@EnableConfigurationProperties(AppConfig.class)
static class TestConfig {
}
}

Spring Cloud Bus for Configuration Refresh

18. Config Server with Bus

Dependencies:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

Configuration:

spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: bus-refresh

Refresh all clients:

curl -X POST http://config-server:8888/actuator/bus-refresh

Refresh specific client:

curl -X POST http://config-server:8888/actuator/bus-refresh/my-client-app:8080

Best Practices

19. Configuration Management Tips

  1. Use profiles for environment-specific configurations
  2. Externalize sensitive data using encryption
  3. Version control your configuration files
  4. Use shared configurations for common settings
  5. Implement proper security with authentication
  6. Monitor configuration changes and their impact
  7. Use fail-fast in clients to detect configuration issues early
  8. Implement retry mechanisms for configuration loading
  9. Use service discovery for dynamic config server location
  10. Regularly backup your configuration repository

Spring Cloud Config Server provides a robust solution for centralized configuration management in microservices architectures, enabling dynamic configuration updates, environment-specific settings, and secure credential management across your distributed systems.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper