Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

Distributed Configuration

In a monolith, application.properties is fine. In microservices, changing a DB password in 50 services means 50 redeployments. We need Externalized Configuration.

1. Spring Cloud Config Server

This server connects to a Git repository (or filesystem) where your config files live, and serves them to microservices via an HTTP API. Setup Server:
  1. Dependency: spring-cloud-config-server.
  2. Annotation: @EnableConfigServer.
  3. application.yml:
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/my-org/config-repo
          default-label: main
The Config Repo: Create a git repo with files:
  • user-service.yml
  • order-service.yml
  • application.yml (Global config shared by all)

2. Config Client

The microservice that consumes the config.
  1. Dependency: spring-cloud-starter-config.
  2. Bootstrap phase: The app needs to know where the Config Server is before it starts up fully.
Create src/main/resources/application.yml (Or bootstrap.yml in older versions):
spring:
  application:
    name: user-service
  config:
    import: "optional:configserver:http://localhost:8888/"
Now, when user-service starts, it fetches configuration from the Config Server.

3. Profiles

You can have user-service-dev.yml and user-service-prod.yml in your git repo. Run your app with: java -jar app.jar --spring.profiles.active=prod It will fetch the prod config automatically.

4. Dynamic Refresh (Hot Reload)

What if you change a property in Git and want to apply it without restarting the service?
  1. Add spring-boot-starter-actuator dependency.
  2. Add @RefreshScope on the bean holding the config.
@RestController
@RefreshScope
public class TestController {

    @Value("${custom.feature-flag}")
    private boolean featureEnabled;

    @GetMapping("/feature")
    public boolean isEnabled() {
        return featureEnabled;
    }
}
  1. Enable the refresh endpoint in application.yml:
management:
  endpoints:
    web:
      exposure:
        include: refresh
  1. Trigger the refresh: POST http://localhost:8080/actuator/refresh
Spring will re-fetch the config and re-initialize beans marked with @RefreshScope.

Interview Deep-Dive

Strong Answer:
  • Spring Cloud Config Server provides versioned, auditable, environment-specific configuration stored in Git. You get full Git history: who changed what, when, and why. You can branch configs for different environments, do pull requests for config changes, and rollback to any previous version with git revert. It serves config via HTTP API, so any language (not just Java) can consume it.
  • Kubernetes ConfigMaps and Secrets are cluster-scoped. They are not versioned beyond etcd’s internal compaction. No PR workflow, no audit trail (unless you use GitOps tools like ArgoCD that sync ConfigMaps from Git). They are tightly coupled to Kubernetes — if you run services on VMs or different clouds, they do not work.
  • Use Config Server when: you need Git-based versioning and audit trails (regulated industries), you have polyglot services or non-Kubernetes deployments, or you need dynamic refresh without restarting pods (Spring’s @RefreshScope re-creates beans on /actuator/refresh without pod restart).
  • Use ConfigMaps/Secrets when: you are fully committed to Kubernetes, your config changes are rare and managed via GitOps (ArgoCD watches a Git repo and syncs ConfigMaps automatically), and you want to avoid running an additional server (Config Server is another thing to deploy, monitor, and keep available).
  • The hybrid approach: use Kubernetes ConfigMaps for infrastructure config (database URLs, broker endpoints) and Spring Cloud Config for application-level feature flags and business rules that change more frequently and need audit trails.
Follow-up: How does @RefreshScope actually work internally? What happens to in-flight requests when a bean is refreshed?@RefreshScope is a custom scope implemented by Spring Cloud. Beans in this scope are stored in a cache inside the RefreshScope bean. When /actuator/refresh is called, Spring Cloud fetches updated config from the Config Server, publishes a RefreshScopeRefreshedEvent, and clears the cache. The next time a refresh-scoped bean is accessed, Spring re-creates it with the new config values. In-flight requests that already hold a reference to the old bean instance continue using it — they are not interrupted. But the next request gets the new instance. There is a brief race window where some threads use the old bean and others use the new one. For most config changes (feature flags, log levels), this is fine. For critical changes (database connection strings), a rolling restart is safer because you want a clean cut.
Strong Answer:
  • Layer 1 — PR review: Config changes go through pull requests like code. Require at least one reviewer. Use branch protection rules on the config repo.
  • Layer 2 — Validation: Add a CI pipeline on the config repo that validates YAML syntax, checks for required properties (database URL, port ranges), and runs a schema validator against a JSON Schema or custom validation rules. Catch typos and missing properties before merge.
  • Layer 3 — Staged rollout: Do not refresh all 50 services at once. Use Spring Cloud Bus (backed by Kafka or RabbitMQ) to broadcast a refresh event, but target specific services or instances. Refresh the canary instance first, verify health, then roll out to the rest.
  • Layer 4 — Fail-safe defaults: Services should start with sensible defaults even if the Config Server is unreachable. Use spring.config.import: optional:configserver:http://... (the optional: prefix). If the server is down, the app starts with its local application.yml defaults. This prevents a Config Server outage from blocking all service startups.
  • Layer 5 — Rollback: Because config is in Git, rollback is git revert on the bad commit and another refresh broadcast. The entire operation takes 30 seconds.
  • The scenario I have seen in production: a developer changed a connection pool size from 10 to 1000 across all services, thinking “more is better.” The database had a 200-connection max. When all 50 services refreshed, they each tried to open 1000 connections. The database rejected them all, every service’s health check failed, and the orchestrator started killing and restarting pods in a cascade. The fix: validation in the CI pipeline that enforces pool size upper bounds.
Follow-up: How do you handle secrets (database passwords, API keys) in a Config Server setup? Plain Git is not acceptable.Options: (1) Spring Cloud Config supports symmetric and asymmetric encryption. Properties like password: {cipher}AQA... are encrypted in Git and decrypted by the Config Server at serve time. The encryption key lives in the Config Server’s environment, not in Git. (2) Use HashiCorp Vault as a backend instead of Git. Spring Cloud Config supports Vault natively — the server fetches secrets from Vault on demand. (3) Use Kubernetes Secrets for sensitive values and Config Server only for non-sensitive config. (4) In AWS, use Parameter Store or Secrets Manager with the Spring Cloud AWS starter, bypassing Config Server entirely for secrets. My recommendation: never store plain-text secrets in Git, even in a private repo. Use Vault or cloud-native secret managers for secrets, and Config Server for everything else.
Strong Answer:
  • Spring profiles activate environment-specific configuration. When you run with --spring.profiles.active=prod, Spring loads application.yml (base), then overlays application-prod.yml (profile-specific). Profile-specific properties override base properties.
  • In Config Server, the same pattern applies per service: user-service.yml (base for all environments), user-service-dev.yml (dev overrides), user-service-prod.yml (prod overrides). The Config Server resolves: /{application}/{profile}/{label} — e.g., /user-service/prod/main fetches the prod config from the main branch.
  • To avoid duplication: put all shared properties in application.yml (global, applies to every service) and service-specific defaults in {service-name}.yml. Profile files should contain ONLY the properties that differ between environments (database URLs, feature flags, log levels). If you find yourself copying the same property across all profile files, it belongs in the base config.
  • Advanced pattern: use a common profile activated in all environments that contains shared infrastructure config (logging format, actuator settings). Then layer environment profiles on top. This creates a clean hierarchy: application.yml (global) -> application-common.yml (shared infra) -> user-service.yml (service defaults) -> user-service-prod.yml (prod overrides).
Follow-up: How do you ensure dev/prod parity when developers use application-dev.yml locally but production uses Config Server?The risk is drift: a developer adds a property in their local application-dev.yml that they forget to add to the Config Server repo. Production crashes on startup because the property is missing. Fix: (1) Run integration tests against Config Server in CI. The test starts the service with spring.profiles.active=prod and spring.config.import=configserver:http://... pointed at a test Config Server loaded from the same Git repo. If a property is missing, the test fails. (2) Use @ConfigurationProperties with @Validated — required properties are enforced at startup with fail-fast. (3) In local development, developers should configure their IDE to point at the Config Server instead of relying on local files. This is the 12-Factor App principle: keep dev and prod as similar as possible.