Knowledge Guide
HomeSystem DesignMicroservices Patterns

Delving into Code An Example

Spring Cloud Config works because a lightweight config server owns a git repository of property files, and every microservice, at startup, calls that server over HTTP with its own identity ({application}/{profile}/{label}) to pull the matching file into its Spring Environment before any bean is wired — so configuration lives outside the deployable artifact and can be versioned, audited, and changed without rebuilding the service.

The config server

Two things make an app a config server: the spring-cloud-config-server dependency and the @EnableConfigServer annotation. This annotation is real and it does exactly what it says — it registers the controllers that expose the resolution endpoints.

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

Point it at a git repo and pick a port in application.yml:

server:
  port: 8888
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/acme/config-repo
          default-label: main

The repo holds one file per service-and-environment, named by convention {application}-{profile}.yml. For an order service in production, order-service-prod.yml might contain:

# order-service-prod.yml (in the git repo)
payment:
  timeout-ms: 3000
  gateway-url: https://pay.acme.com

The client — no @EnableConfigClient exists

Here is the correction to the widely-copied mistake: there is no @EnableConfigClient annotation in Spring Cloud Config. Adding it will not compile. A client needs only the starter dependency plus a small amount of configuration that tells it (a) who it is and (b) where the server lives.

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

In Spring Boot 2.4+ the server is reached via spring.config.import (the old bootstrap.yml approach needs the extra spring-cloud-starter-bootstrap dependency):

# application.yml of the CLIENT
spring:
  application:
    name: order-service      # → the {application} slug
  profiles:
    active: prod             # → the {profile} slug
  config:
    import: "configserver:http://localhost:8888"
management:
  endpoints:
    web:
      exposure:
        include: refresh     # so /actuator/refresh is reachable

The main class stays a plain @SpringBootApplication — no config-client annotation at all.

Reading a value — and why @Value alone won't refresh

@Value injects a resolved property into a field once, at bean construction:

@RefreshScope        // REQUIRED for /actuator/refresh to re-bind this bean
@Component
public class PaymentClient {

    @Value("${payment.timeout-ms}")
    private int timeoutMs;   // 3000 at startup, from order-service-prod.yml

    public int timeoutMs() { return timeoutMs; }
}

Why the naive version is wrong: a common claim is that POST /actuator/refresh gives you "config changes without a restart" for every bean. It does not. Refresh fires an EnvironmentChangeEvent and only re-instantiates beans annotated @RefreshScope (plus it re-binds @ConfigurationProperties beans, which get refresh behaviour for free). A plain singleton with a bare @Value field keeps its old value forever until the process restarts, because that field was set once during construction and nothing tells Spring to rebuild the bean. Drop the @RefreshScope above and the timeout stays 3000 no matter how many times you hit refresh.

A concrete trace: change a timeout live

#Action / callWhat happenstimeoutMs
1order-service bootsClient reads spring.config.import, calls GET http://localhost:8888/order-service/prod
2Server resolvesClones/pulls the git repo, reads order-service-prod.yml, returns JSON property sources
3Client mergesValues land in the Environment; PaymentClient is constructed3000
4Ops edits gitpayment.timeout-ms: 5000, commit + push to main3000 (stale)
5POST /actuator/refreshClient re-fetches from server, fires EnvironmentChangeEvent; @RefreshScope bean is discarded3000 → pending
6Next call to timeoutMs()PaymentClient is lazily re-created with the new value5000

Note step 6: @RefreshScope beans are rebuilt lazily on next access, not eagerly at refresh time — the in-flight request that triggered refresh may still see the old bean.

diagram
diagram

Encrypting secrets

The encrypt.key is a symmetric key set on the server — and you should supply it as an environment variable (ENCRYPT_KEY=...), never commit it to the config repo, since it decrypts everything. With it set, the server exposes /encrypt and /decrypt:

$ curl -s http://localhost:8888/encrypt -d 'db-password-123'
AQBc9f2e7a...        # opaque cipher text

Store the cipher in git with a {cipher} prefix; the server decrypts it before handing the value to any client, so the client sees plaintext and code is unchanged:

# order-service-prod.yml
spring:
  datasource:
    password: '{cipher}AQBc9f2e7a...'

For real deployments, prefer an asymmetric RSA keystore (encrypt.key-store.*) so servers can serve decrypted values without any node holding the symmetric secret.

Pitfalls

When to use it — and when not to

Signals that point to a config server: you are already in the Spring ecosystem; you want config versioned in git with commit history and rollback; you have many services sharing common values; and you want to flip a value (a feature flag, a timeout) without a redeploy pipeline.

Trade-offs vs. Kubernetes ConfigMaps/Secrets. ConfigMaps mount config as env vars or files with zero extra infrastructure and are the natural fit if you are already on k8s. What you gain with them: simplicity and no runtime dependency. What you lose: git-native history is not built in, and updating a ConfigMap typically means rolling the pods (a restart) — you trade dynamic refresh for immutability. Choose Spring Cloud Config when you want git-backed audit trails and live refresh across a Spring fleet; prefer ConfigMaps when you are k8s-native and a redeploy-per-change is acceptable.

Trade-offs vs. HashiCorp Vault / AWS Parameter Store. Config Server's encryption is bolted on: a shared symmetric key and manual /encrypt round-trips, with git as the store (no fine-grained per-secret ACLs, no leasing, no rotation). Vault gives dynamic short-lived secrets, strong access policies, and audit logging — at the cost of running and operating Vault. Choose Vault when secrets are the primary concern; keep Config Server for non-secret application config (and even then, Spring integrates with Vault as a backend, so the two compose).

Takeaways


Re-authored and deepened for this guide. Sources: Spring Cloud Config reference documentation (docs.spring.io, Config Server & Client, Encryption/Decryption, and Refresh Scope sections); Spring Boot Actuator reference (the refresh endpoint and endpoint exposure); Spring Cloud Bus documentation (busrefresh fan-out); and Chris Richardson, Microservices Patterns (Manning), Externalized Configuration pattern. Corrects the non-existent @EnableConfigClient annotation and the incomplete "refresh without restart" claim.

🤖 Don't fully get this? Learn it with Claude

Stuck on Delving into Code An Example? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Delving into Code An Example** (System Design) and want to truly understand it. Explain Delving into Code An Example from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Delving into Code An Example** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Delving into Code An Example** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Delving into Code An Example** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes