Contents
- Architecture Overview
- Config Server Setup
- Git Repository Structure
- Client Service Setup
- Profile-Specific & Label-Based Config
- Live Refresh with @RefreshScope
- Encrypting Sensitive Properties
- Securing the Config Server
The Config Server acts as an HTTP endpoint that serves properties files pulled from a Git repository. Each client service fetches its configuration by providing its application name, active profile, and label (branch/tag/commit).
- Config Server — a Spring Boot app with @EnableConfigServer; reads from Git, filesystem, Vault, or JDBC
- Config Client — any Spring Boot service with spring-cloud-starter-config; fetches properties at startup
- Config repo — a plain Git repo with YAML/properties files named {app}-{profile}.yml
The Config Server is stateless — it simply proxies properties from the backing Git repo. Scale it horizontally behind a load balancer and point all services at the same URL. The Git clone is cached locally per server instance.
Add the following to your pom.xml (Maven) or build.gradle (Gradle). Spring Boot manages compatible versions automatically when you use the Spring Boot BOM.
<!-- pom.xml — Config Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
@SpringBootApplication
@EnableConfigServer // single annotation turns on the Config Server
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
# application.yml — Config Server
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/my-org/config-repo # or git@github.com:...
default-label: main # branch to use
clone-on-start: true # eager clone at startup
search-paths: # subdirectories to search
- '{application}' # per-app subfolder
# For private repos:
username: ${GIT_USERNAME}
password: ${GIT_TOKEN}
# Or SSH key:
# private-key: ${GIT_PRIVATE_KEY}
management:
endpoints:
web:
exposure:
include: health,info,refresh,bus-refresh
Name files using the pattern {application}-{profile}.yml. Files are resolved in priority order: most specific first, then falling back to more generic.
config-repo/
├── application.yml # shared defaults for ALL services
├── application-prod.yml # shared prod overrides for ALL services
│
├── order-service.yml # order-service defaults (all profiles)
├── order-service-dev.yml # order-service dev overrides
├── order-service-prod.yml # order-service prod overrides
│
├── payment-service.yml
├── payment-service-prod.yml
│
└── inventory-service.yml
# application.yml — shared defaults for all services
logging:
level:
root: INFO
management:
endpoints:
web:
exposure:
include: health,info
---
# order-service-prod.yml
spring:
datasource:
url: jdbc:postgresql://prod-db.internal:5432/orders
username: orders_app
password: '{cipher}AQABAAEAgBJ...' # encrypted value
app:
order:
timeout-seconds: 30
max-retries: 3
Verify the server is serving config correctly by hitting the endpoint directly:
# GET /{application}/{profile}/{label}
curl http://localhost:8888/order-service/prod/main
curl http://localhost:8888/order-service/dev
# Response includes all resolved property sources in priority order
Add the following to your pom.xml (Maven) or build.gradle (Gradle). Spring Boot manages compatible versions automatically when you use the Spring Boot BOM.
<!-- pom.xml — Config Client (each microservice) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
# application.yml — Config Client
spring:
application:
name: order-service # determines which files are fetched from config repo
config:
import: "optional:configserver:http://localhost:8888"
# "optional:" means app starts even if server is down
profiles:
active: dev
cloud:
config:
fail-fast: true # fail startup if config server unreachable (prod)
retry:
max-attempts: 6
initial-interval: 1000
multiplier: 1.5
max-interval: 2000
// Properties fetched from Config Server are injected normally
@Service
public class OrderService {
@Value("${app.order.timeout-seconds:10}")
private int timeoutSeconds;
@Value("${app.order.max-retries:3}")
private int maxRetries;
}
// Or use @ConfigurationProperties for type-safe binding
@ConfigurationProperties(prefix = "app.order")
@Component
public class OrderProperties {
private int timeoutSeconds = 10;
private int maxRetries = 3;
// getters/setters
}
The YAML below shows the complete configuration for this feature. Adjust the values to match your environment.
# Priority order (highest to lowest) for order-service with profile=prod, label=main:
# 1. order-service-prod.yml (app + profile specific)
# 2. order-service.yml (app specific, all profiles)
# 3. application-prod.yml (shared prod overrides)
# 4. application.yml (shared defaults)
# Labels — serve config from a specific branch or tag
spring:
cloud:
config:
label: release/v2.1 # fetch from this Git branch/tag
# Config Server also serves native file format (e.g., for feature branches)
# Composite backend — Git for prod, filesystem for local dev
spring:
cloud:
config:
server:
composite:
- type: git
uri: https://github.com/my-org/config-repo
- type: native
search-locations: file:./local-config
Without restart, services can reload config by calling the /actuator/refresh endpoint (or using Spring Cloud Bus for fan-out). Beans annotated with @RefreshScope are re-created on refresh.
// Mark the bean as refreshable
@RefreshScope
@Service
public class FeatureFlagService {
@Value("${feature.new-checkout-enabled:false}")
private boolean newCheckoutEnabled;
public boolean isNewCheckoutEnabled() {
return newCheckoutEnabled; // re-injected after refresh
}
}
# Trigger a refresh on one service instance
curl -X POST http://order-service:8080/actuator/refresh
# Fan-out refresh to ALL instances via Spring Cloud Bus (Kafka or RabbitMQ)
# Add: spring-cloud-starter-bus-kafka to all services
curl -X POST http://config-server:8888/actuator/bus-refresh
# Or refresh only services with a specific destination
curl -X POST "http://config-server:8888/actuator/bus-refresh/order-service:**"
@RefreshScope does NOT work with @ConfigurationProperties beans directly — those need @RefreshScope on the class OR you can use Spring Cloud's auto-rebinding by adding spring-cloud-context which auto-refreshes @ConfigurationProperties beans.
Config Server has built-in encryption/decryption. Store ciphered values in Git — Config Server decrypts them before serving to clients.
# Config Server application.yml — set the encryption key
encrypt:
key: my-symmetric-key # or use asymmetric RSA key pair
# For RSA (more secure):
# key-store:
# location: classpath:keystore.jks
# password: ${KEYSTORE_PASSWORD}
# alias: config-server-key
# Encrypt a value
curl -X POST http://localhost:8888/encrypt -d 'my-secret-password'
# Returns: AQABAAEAgBJ3xFq7...
# Decrypt (for verification only — clients never call this)
curl -X POST http://localhost:8888/decrypt -d 'AQABAAEAgBJ3xFq7...'
# Returns: my-secret-password
# Store in Git config file with {cipher} prefix
spring:
datasource:
password: '{cipher}AQABAAEAgBJ3xFq7...'
# Config Server decrypts this before sending to the client
# Client receives the plaintext value
For production use, prefer HashiCorp Vault as the Config Server backend instead of Git-based encryption. Spring Cloud Vault integrates natively and provides dynamic secrets, lease rotation, and fine-grained access policies.
The YAML below shows the complete configuration for this feature. Adjust the values to match your environment.
# Config Server — add Spring Security HTTP Basic auth
# pom.xml: spring-boot-starter-security
spring:
security:
user:
name: config-admin
password: ${CONFIG_SERVER_PASSWORD}
# Config Client — provide credentials
spring:
cloud:
config:
uri: http://localhost:8888
username: config-admin
password: ${CONFIG_SERVER_PASSWORD}
# Production pattern — put Config Server behind service mesh or API gateway
# and use mutual TLS or OAuth2 token-based auth instead of HTTP Basic
spring:
cloud:
config:
tls:
enabled: true
key-store: classpath:client-keystore.p12
key-store-password: ${TLS_PASSWORD}
trust-store: classpath:truststore.p12
trust-store-password: ${TLS_TRUST_PASSWORD}