Spring Cloud Config & Centralised Configuration
Centralised configuration done safely: Config Server with a Git backend, client bootstrap and fail-fast, @RefreshScope, encrypted values, and when Kubernetes ConfigMaps are enough.
On this page
Centralised configuration solves a real problem: fifteen services each with their own copy of a database URL, and a rotation that requires fifteen deploys. It also introduces a dependency every service needs at startup, which has to be designed for.
It is worth asking up front whether you need it at all. On Kubernetes, ConfigMaps and Secrets already give you per-environment configuration delivered by the platform, versioned in the same Git repository as the manifests, with no extra service to run or keep available. Spring Cloud Config earns its place when you are not on such a platform, when configuration must change without a pod restart, or when a single change needs to reach many services at once — not simply because the estate has become distributed.
Key Takeaways
- Git as the backend gives configuration history, review and rollback for free.
- Enable fail-fast plus retry — starting with missing config is worse than not starting.
@RefreshScopeonly suits small stateless beans; structural changes need a restart.- Spring Cloud Bus broadcasts a refresh to every instance instead of curling each one.
- Encrypt secrets at rest with
{cipher}, or better, keep them in Vault.
Server
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: git@github.com:acme/service-config.git
default-label: main
clone-on-start: true # fail at startup, not on first request
force-pull: true # discard local drift
search-paths: '{application}' # a directory per service
timeout: 10
private-key: ${CONFIG_REPO_SSH_KEY}
# Composite backend: Vault for secrets, Git for everything else.
vault:
host: vault.internal
port: 8200
scheme: https
backend: secret
order: 1
encrypt:
key-store:
location: file:/etc/config-server/keystore.jks
password: ${KEYSTORE_PASSWORD}
alias: config-keyThe repository layout maps directly onto the resolution rules:
service-config/
├── application.yml # defaults for every service
├── application-prod.yml # prod defaults for every service
├── order-service/
│ ├── order-service.yml # service defaults
│ └── order-service-prod.yml # service + profile — highest precedence
└── payment-service/
└── payment-service.ymlProperties merge from least to most specific, so application.yml provides the base and
order-service-prod.yml overrides only what differs. This is the main advantage over per-service
files: a change to a shared value happens once, is reviewed once, and applies everywhere.
Client
spring:
application:
name: order-service # selects the directory in the repo
config:
import: 'configserver:http://config-server:8888'
cloud:
config:
# Refuse to start without configuration rather than booting with defaults
# that silently point at the wrong database.
fail-fast: true
retry:
initial-interval: 1000
max-interval: 5000
max-attempts: 6
label: main
profile: ${SPRING_PROFILES_ACTIVE:default}Note spring.config.import rather than the old bootstrap.yml. Since Spring Boot 2.4 the bootstrap
context is legacy, and the import syntax is the supported path. Leave off the optional: prefix in
production so a missing Config Server is an error rather than a silent fallback to packaged defaults
— which is exactly the failure that sends a service at a development database.
Refreshing without a restart
@Component
@RefreshScope
@ConfigurationProperties(prefix = "app.features")
public class FeatureFlags {
private boolean newCheckoutFlow;
private int maxBasketSize;
// getters and setters
}@RefreshScope works by replacing the bean with a proxy that recreates the target after a refresh
event. That is fine for a small stateless holder of values and actively harmful for anything holding
a resource — a DataSource, a Kafka consumer, a thread pool. Recreating those mid-flight drops
connections and loses in-progress work.
The practical rule: refresh behavioural configuration such as flags, thresholds and timeouts; restart for structural configuration such as connection URLs, pool sizes and topic names. A rolling restart is cheap and predictable; a half-refreshed connection pool is neither.
Encryption
curl -s config-server:8888/encrypt -d 'my-database-password'
# AQBc9nJt4kL...spring:
datasource:
password: '{cipher}AQBc9nJt4kL...'The client receives the decrypted value transparently, so secrets stay out of plaintext in Git while keeping review and history.
It is still weaker than a secret manager. The value is decrypted by Config Server, so anyone who can read from Config Server can read every secret; rotation means re-encrypting and committing; and there is no audit trail of who read what. For anything genuinely sensitive, use the Vault backend — dynamic database credentials with short leases mean a leaked credential expires on its own.
Do you actually need it?
On Kubernetes, ConfigMaps and Secrets already externalise configuration, the platform distributes
them, and spring-cloud-kubernetes can watch a ConfigMap and trigger a refresh. For a single-cluster
deployment that is usually enough, and it is one less service to run, secure and page someone about.
Config Server earns its cost in three situations: when you want configuration reviewed and versioned in Git with the same process as code; when you deploy across several platforms or clouds that do not share a configuration mechanism; or when you need dynamic refresh with a clear audit trail of who changed what and when.
Whichever you choose, treat the configuration source as critical infrastructure. It is a startup dependency for every service, so it needs replicas, monitoring, and a restore path you have actually tested — a Config Server whose Git credentials expired at 3am is an outage for the entire estate.
What to take away
Back Config Server with Git for history and review, and Vault for secrets. Turn on fail-fast with
retry so services never boot half-configured. Use @RefreshScope narrowly and restart for structural
change. And if you are on one Kubernetes cluster, check whether ConfigMaps already do what you need
before adding another service to operate.
Frequently Asked Questions
Do I need Config Server on Kubernetes?
What happens if Config Server is down when a service starts?
Is @RefreshScope safe to use everywhere?
Related tutorials
- Microservices Security PatternsSecuring a distributed system: authentication at the edge, mTLS and workload identity between services, token propagation without over-trust, API keys and secret management.
- The Microservices Observability StackAssembling logs, metrics and traces into something usable: PromQL that answers real questions, the Grafana stack, golden signals, and alerts that mean something.
- Service Mesh with IstioWhat a service mesh moves out of your application: automatic mTLS, VirtualService routing, outlier detection, authorization policies, fault injection and progressive delivery.
- Kubernetes for Java MicroservicesRunning Spring Boot on Kubernetes properly: liveness versus readiness probes, JVM memory inside cgroups, resource requests and limits, autoscaling, and zero-downtime rollouts.