The Problem
You have 20 microservices. Each has its own application.yml with database URLs, API keys, feature flags. Now you need to change the database password. Update 20 files? Redeploy 20 services? What if you miss one?
Config Server centralizes all configuration. Change once, all services pick it up.
// WITHOUT Config Server user-service/application.yml → db.url=jdbc:mysql://old-db:3306 order-service/application.yml → db.url=jdbc:mysql://old-db:3306 payment-service/application.yml → db.url=jdbc:mysql://old-db:3306 // Change all 20 manually... // WITH Config Server config-repo/application.yml → db.url=jdbc:mysql://new-db:3306 // All services automatically get the new value!
Single Source of Truth
All configuration in one Git repository.
Environment Specific
Different values for dev, staging, production.
Dynamic Updates
Change config without redeploying services.
Config Server Setup
<!-- pom.xml for Config Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
# application.yml for Config Server
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-org/config-repo
default-label: main
search-paths: '{application}' # Optional: folder per service
Config Repository Structure
config-repo/ ├── application.yml # Shared by ALL services ├── application-dev.yml # Shared dev config ├── application-prod.yml # Shared prod config ├── user-service.yml # Specific to user-service ├── user-service-dev.yml # user-service dev overrides ├── user-service-prod.yml # user-service prod overrides ├── order-service.yml └── payment-service.yml
# application.yml (shared)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
logging:
level:
root: INFO
# user-service.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/users
user:
default-role: USER
max-login-attempts: 5
# user-service-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db.example.com:3306/users
user:
max-login-attempts: 3
Config Client Setup
<!-- pom.xml for any microservice -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
# application.yml for user-service
spring:
application:
name: user-service # Must match filename in config repo
profiles:
active: dev
config:
import: optional:configserver:http://localhost:8888
# Service starts → Fetches user-service.yml + user-service-dev.yml
Accessing Configuration
@RestController
public class UserController {
@Value("${user.default-role}")
private String defaultRole;
@Value("${user.max-login-attempts}")
private int maxLoginAttempts;
// Or use @ConfigurationProperties for groups
}
@ConfigurationProperties(prefix = "user")
@Component
public class UserProperties {
private String defaultRole;
private int maxLoginAttempts;
// getters, setters
}
You can also fetch config directly from Config Server:
# Get user-service config for dev profile
GET http://localhost:8888/user-service/dev
# Response (JSON):
{
"name": "user-service",
"profiles": ["dev"],
"propertySources": [
{
"name": "user-service-dev.yml",
"source": {
"user.default-role": "USER",
"user.max-login-attempts": 5
}
}
]
}
Dynamic Refresh
Change config without restart using Spring Cloud Bus:
<!-- Add to services -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml
management:
endpoints:
web:
exposure:
include: refresh, busrefresh
spring:
rabbitmq:
host: localhost
port: 5672
# After changing config in Git: # Refresh single service POST http://localhost:8080/actuator/refresh # Refresh ALL services via message bus POST http://localhost:8080/actuator/busrefresh
// Mark beans that should refresh
@RefreshScope
@Component
public class UserProperties {
@Value("${user.max-login-attempts}")
private int maxLoginAttempts;
// Will update when /refresh is called
}
Encrypting Secrets
# Config Server application.yml
encrypt:
key: my-secret-encryption-key # Or use keystore
# In config repo - store encrypted values:
spring:
datasource:
password: '{cipher}AQB+...encrypted...'
# Config Server decrypts before sending to clients
# Encrypt a value POST http://localhost:8888/encrypt Body: my-secret-password → Returns: AQB+...encrypted... # Decrypt a value POST http://localhost:8888/decrypt Body: AQB+...encrypted... → Returns: my-secret-password
High Availability
# Run multiple Config Server instances behind load balancer
# Clients can specify multiple servers:
spring:
config:
import: >
optional:configserver:http://config1:8888,
optional:configserver:http://config2:8888
# With Eureka (recommended):
spring:
cloud:
config:
discovery:
enabled: true
service-id: config-server
Vault Integration
For sensitive secrets, use HashiCorp Vault instead of Git:
spring:
cloud:
config:
server:
vault:
host: vault.example.com
port: 8200
scheme: https
authentication: TOKEN
token: ${VAULT_TOKEN}
Best Practices
- Use Git: Version control your configuration with history
- Encrypt secrets: Never store passwords in plain text
- Separate environments: Different branches or folders for dev/prod
- Use profiles: application-{profile}.yml for environment-specific config
- Cache config: Clients should cache and handle Config Server downtime
- Monitor changes: Track who changed what and when