Centralized Configuration in Spring Boot Microservices Is Not Optional

by Eric Hanson, Backend Developer at Clean Systems Consulting

How Configuration Debt Accumulates

It starts reasonably. One Spring Boot service, an application.yml per environment, secrets injected as environment variables by the deployment pipeline. The setup is simple because the system is simple.

Then you add services. Each one gets its own application.yml, its own application-prod.yml, its own set of environment variables defined somewhere in a CI/CD pipeline YAML that four people have edit access to. A database connection string changes. You update it in three places, miss the fourth, and spend an hour on an incident that boils down to one service pointing at the wrong host.

Or worse: a credential rotates, you update it in the secrets manager, but three services are still reading the old value from environment variables baked into their container definitions at deploy time — and won't pick up the change until someone manually redeploys each one.

At five services, this is annoying. At fifteen, it is a recurring source of production incidents. Centralized configuration is the fix, and the right time to implement it was before the fifth service, not after the fifteenth.

What Centralized Configuration Actually Means

Not a shared application.yml committed to a monorepo. That solves the co-location problem but not the runtime problem — services still need to be redeployed to pick up changes, and secrets still live in version control.

A real centralized configuration system has three properties:

Single source of truth — one place to define a configuration value, no matter how many services consume it. Changing it once propagates everywhere without touching individual service deployments.

Environment awareness — the same key resolves to different values per environment without duplication of the service-level config structure.

Secret separation — non-sensitive config and sensitive credentials are managed differently, with credentials stored in a secrets manager and never in version control or environment variable definitions.

Spring Boot gives you two mature paths: Spring Cloud Config Server backed by a Git repository, and native integration with HashiCorp Vault or cloud-native secrets managers (AWS Secrets Manager, GCP Secret Manager). In practice, most production systems end up using both — Config Server for application properties, Vault or a cloud secrets manager for credentials.

Spring Cloud Config Server: The Baseline

Config Server is a Spring Boot application that serves configuration over HTTP from a backing Git repository. Each client service bootstraps by calling the Config Server on startup, receives its resolved properties, and merges them with local values.

The server setup is minimal:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}
# application.yml for the Config Server itself
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-org/service-config
          default-label: main
          search-paths: '{application}'
          clone-on-start: true

The Git repository structure mirrors the Spring profile convention. For a service named payment-service:

service-config/
  payment-service/
    application.yml          # shared across all environments
    application-dev.yml
    application-staging.yml
    application-prod.yml
  order-service/
    application.yml
    application-prod.yml
  application.yml            # global defaults, all services

On the client side, each service declares its Config Server endpoint in bootstrap.yml — which loads before application.yml and before the application context initializes:

# bootstrap.yml in each client service
spring:
  application:
    name: payment-service
  cloud:
    config:
      uri: http://config-server:8888
      fail-fast: true
      retry:
        max-attempts: 6
        initial-interval: 1000
        multiplier: 1.5

fail-fast: true is important. Without it, a service will start successfully even if it cannot reach the Config Server, falling back to local properties. In production, that means a service silently running with stale or missing configuration. Fail fast, restart, alert — that's the correct behavior when config is unavailable.

Live Refresh Without Redeployment

The first thing teams want after setting up Config Server is live config refresh — changing a value in the Git repo and having running services pick it up without restarting. Spring Cloud Bus with Kafka or RabbitMQ handles this.

When a config change is pushed to the Git repository, a webhook triggers a /actuator/busrefresh call on the Config Server. Spring Cloud Bus broadcasts a refresh event to all connected services over the message broker. Each service re-fetches its configuration and rebinds @RefreshScope beans.

@RestController
@RefreshScope
public class FeatureFlagController {

    @Value("${features.new-checkout-flow.enabled:false}")
    private boolean newCheckoutFlowEnabled;

    @GetMapping("/checkout")
    public ResponseEntity<?> checkout() {
        if (newCheckoutFlowEnabled) {
            return newCheckoutHandler.handle();
        }
        return legacyCheckoutHandler.handle();
    }
}

The @RefreshScope annotation means this bean is destroyed and recreated on a refresh event, picking up the new property value. Beans without @RefreshScope are not refreshed — they hold their initial values until the service restarts.

This is a meaningful operational capability for feature flags and non-sensitive tuning parameters. It is not the right mechanism for credential rotation — that belongs in Vault.

Secrets Belong in Vault, Not Git

Storing secrets in the Config Server's Git repository — even a private one, even encrypted — is the wrong model. Git history is permanent, access controls are coarse, and rotation requires a commit.

HashiCorp Vault integrates directly with Spring Boot via Spring Cloud Vault. Services authenticate to Vault using Kubernetes service account tokens (in a Kubernetes deployment) or AppRole credentials, and receive secrets scoped to their identity:

# bootstrap.yml addition for Vault integration
spring:
  cloud:
    vault:
      uri: https://vault.internal:8200
      authentication: KUBERNETES
      kubernetes:
        role: payment-service
      kv:
        enabled: true
        backend: secret
        default-context: payment-service

Vault serves secrets at secret/payment-service as Spring-compatible properties. A database credential at secret/payment-service/database.password becomes available as ${database.password} in the application context, indistinguishable from any other property — but sourced from Vault, rotated in Vault, audited in Vault.

Dynamic secrets are Vault's strongest capability for databases: instead of a static password shared across all instances, Vault generates a unique credential per service instance with a TTL, and rotates it automatically. The service never handles a long-lived credential. If a credential leaks, its blast radius is bounded by the TTL.

Spring Cloud Vault's database secrets backend with the Postgres plugin:

spring:
  cloud:
    vault:
      database:
        enabled: true
        role: payment-service-db-role
        backend: database

The datasource URL and username come from application.yml. The password is injected by Vault at startup and renewed before expiry. The service code sees a standard Spring datasource and has no awareness of the credential lifecycle.

The Kubernetes-Native Alternative

If you're running on Kubernetes and not ready to operate Vault, Kubernetes Secrets surfaced as environment variables or mounted volumes, combined with External Secrets Operator pulling from AWS Secrets Manager or GCP Secret Manager, covers most of the same ground with less operational overhead.

External Secrets Operator syncs a SecretStore (pointing at AWS Secrets Manager, for example) into native Kubernetes Secrets on a configurable refresh interval:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: payment-service-db-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: payment-service-db-credentials
  data:
    - secretKey: database.password
      remoteRef:
        key: prod/payment-service/database
        property: password

The Spring Boot service mounts the resulting Kubernetes Secret as environment variables or a volume, and reads them as standard Spring properties. Rotation happens in AWS Secrets Manager; ESO syncs the change into the cluster; the pod picks it up on its next restart or via Spring's config refresh if you've wired it that way.

This is operationally simpler than self-managed Vault for teams that are already deep in AWS or GCP and don't want another stateful system to run.

What You Need Before the Sixth Service

You do not need all of this on day one. You do need it before the complexity of managing configuration per-service manually starts causing incidents.

The minimum viable centralized config setup: Spring Cloud Config Server backed by a private Git repository, fail-fast enabled on all clients, and credentials sourced from your cloud provider's secrets manager rather than environment variables in your pipeline definition. That covers the failure modes that actually happen — stale config after a credential rotation, config drift between environments, secrets committed to version control by accident.

Vault and dynamic credentials are the next step when the team has the operational capacity to run a stateful secrets system. External Secrets Operator is the right choice if you're on Kubernetes and already in a cloud provider's ecosystem.

The configuration you scatter across a dozen services today is the incident you investigate at 2am next quarter. Centralize it before that happens, not after.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Why Startups That Hire Async Backend Contractors Ship Faster Than Those That Don't

It's not about the contractors being faster. It's about the model removing the delays that slow down teams waiting on local hiring.

Read more

The Night Before a Deadline: Panic, Coffee, and Code

Every junior contractor knows this scene all too well. The deadline looms, caffeine flows, and code becomes both friend and foe.

Read more

Why Good Backend Engineers Rarely Work on Fiverr

Ever browsed Fiverr for a backend developer and wondered why most gigs feel… shallow? The truth is, top backend engineers rarely show up there—and for good reasons.

Read more

Why Auckland Startups Have an Unfair Advantage When They Hire Async — and Most Don't Know It Yet

Auckland sits in one of the earliest timezones on the planet. Most founders see that as isolation. It's actually a scheduling superpower.

Read more