Distributed Configuration

In a monolith, configuration lives in one place and restarts propagate it instantly. In a distributed system, dozens of services each carry their own copy — and keeping them consistent, up-to-date, and environment-aware without a restart is a hard problem. Distributed configuration management solves it by externalising config into a shared source of truth that services read from (pull) or are notified by (push).

The Problem

As a system grows, configuration scattered across services creates several failure modes:

  1. Configuration drift — services deployed at different times accumulate different config values. Environment variables set manually on one host silently diverge from others. What works in staging breaks in production.
  2. Restart-coupled changes — baking config into the image or environment variables means changing a timeout or a feature flag requires a full redeploy, even when the change is trivial.
  3. Secret sprawl — credentials duplicated across services and environments are hard to rotate and easy to leak.
  4. No audit trail — ad-hoc config changes have no history, no rollback, and no accountability.

Pull vs Push

There are two fundamental models for how services receive updated configuration:

PullPush
How it worksService polls the config source on startup (and optionally on an interval)Config source notifies services when a value changes
LatencyDelayed by poll interval — stale until next checkNear real-time — services receive the change immediately
ComplexitySimple — service just reads a URL on bootRequires a pub/sub channel or long-poll connection per service instance
Restart required?Yes — unless the service supports live reloadNo — service reacts to the push event at runtime
ExamplesSpring Cloud Config (default), AWS SSM Parameter StoreSpring Cloud Bus + Config, etcd watches, AWS AppConfig, LaunchDarkly

In practice most systems use a hybrid: pull on startup for baseline config, push for runtime changes that must propagate without a restart (feature flags, rate limits, kill switches).

Config Server (Spring Cloud)

Spring Cloud Config provides a Git-backed config server and a client library that makes externalised configuration the default in a Spring Boot microservices stack.

Server setup

// build.gradle
implementation 'org.springframework.cloud:spring-cloud-config-server'

// ConfigServerApplication.java
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}
# application.yml (config server)
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-org/config-repo
          clone-on-start: true
          default-label: main

The server exposes config over HTTP at {service-name}/{profile}. Clients fetch their config on startup: GET /order-service/production.

Client setup (pull)

// build.gradle
implementation 'org.springframework.cloud:spring-cloud-starter-config'

# bootstrap.yml (client — loaded before application.yml)
spring:
  application:
    name: order-service
  config:
    import: optional:configserver:http://config-server:8888

Push with Spring Cloud Bus

To push changes at runtime without restarting services, connect them via a message broker (RabbitMQ or Kafka). A POST /actuator/busrefresh on any service broadcasts a refresh event to all subscribers.

# add to every service
implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp'

# application.yml (each service)
spring:
  rabbitmq:
    host: rabbitmq
    port: 5672
management:
  endpoints:
    web:
      exposure:
        include: busrefresh
# trigger a push refresh across all services
POST http://any-service:PORT/actuator/busrefresh

AWS AppConfig (managed push)

For AWS environments, AppConfig manages config as versioned deployments with built-in rollout strategies (linear, exponential, all-at-once) and automatic rollback on CloudWatch alarms. Clients poll a local agent sidecar which handles the push lifecycle internally.

// Java SDK — poll the local AppConfig agent (runs as a sidecar)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(
        "http://localhost:2772/applications/MyApp" +
        "/environments/Production/configurations/MyConfig"))
    .build();

HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
String configJson = response.body();

Feature Flags

Feature flags are push-based configuration taken to its logical conclusion: any boolean or multi-variant value that can be changed in production without a deploy, per user, per cohort, or per environment.

  1. Release flags — hide an incomplete feature behind a flag so it ships to production dark and is enabled when ready, decoupling deploy from release.
  2. Kill switches — disable a non-critical feature instantly if it causes errors, without a rollback deploy.
  3. Canary / percentage rollouts — enable a feature for 5% of users, monitor metrics, and expand incrementally.
  4. A/B testing — serve different variants to different user segments and measure which performs better.

LaunchDarkly (Java SDK)

LDConfig config = new LDConfig.Builder().build();
LDClient client = new LDClient("sdk-key", config);

LDContext context = LDContext.builder("user-123")
    .set("plan", "premium")
    .build();

boolean showNewCheckout = client.boolVariation("new-checkout-flow", context, false);

if (showNewCheckout) {
    return newCheckoutService.render(cart);
} else {
    return legacyCheckoutService.render(cart);
}

LaunchDarkly streams flag changes to connected SDK instances — the flag value returned by boolVariation reflects the live state, no restart or poll needed.

Best Practices

  1. Never bake secrets into config files. Use a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault) for credentials and inject them at runtime. Config files in Git should hold non-sensitive settings only.
  2. Version and audit every change. A Git-backed config server gives you a full history, diff, and rollback for free. For managed services, ensure CloudTrail or equivalent is on.
  3. Validate config before it ships. A bad config value should fail loudly on the server, not silently corrupt live services. Schema validation and staged rollouts (AppConfig deployments, flag percentage rollouts) reduce blast radius.
  4. Design services to tolerate stale config. If the config server is unavailable, services should start from a cached local copy rather than refusing to boot. Spring Cloud Config supports this with spring.cloud.config.fail-fast=false and a local fallback.
  5. Clean up flags. Feature flags are technical debt. Every live flag is a branch in the code. Permanent flags should become proper config; dead flags should be deleted.