Contents

The Config Server acts as an HTTP endpoint that serves properties files pulled from a Git repository. Each client service fetches its configuration by providing its application name, active profile, and label (branch/tag/commit).

The Config Server is stateless — it simply proxies properties from the backing Git repo. Scale it horizontally behind a load balancer and point all services at the same URL. The Git clone is cached locally per server instance.
<!-- pom.xml — Config Server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> @SpringBootApplication @EnableConfigServer // single annotation turns on the Config Server public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } } # application.yml — Config Server server: port: 8888 spring: application: name: config-server cloud: config: server: git: uri: https://github.com/my-org/config-repo # or git@github.com:... default-label: main # branch to use clone-on-start: true # eager clone at startup search-paths: # subdirectories to search - '{application}' # per-app subfolder # For private repos: username: ${GIT_USERNAME} password: ${GIT_TOKEN} # Or SSH key: # private-key: ${GIT_PRIVATE_KEY} management: endpoints: web: exposure: include: health,info,refresh,bus-refresh

Name files using the pattern {application}-{profile}.yml. Files are resolved in priority order: most specific first, then falling back to more generic.

config-repo/ ├── application.yml # shared defaults for ALL services ├── application-prod.yml # shared prod overrides for ALL services │ ├── order-service.yml # order-service defaults (all profiles) ├── order-service-dev.yml # order-service dev overrides ├── order-service-prod.yml # order-service prod overrides │ ├── payment-service.yml ├── payment-service-prod.yml │ └── inventory-service.yml # application.yml — shared defaults for all services logging: level: root: INFO management: endpoints: web: exposure: include: health,info --- # order-service-prod.yml spring: datasource: url: jdbc:postgresql://prod-db.internal:5432/orders username: orders_app password: '{cipher}AQABAAEAgBJ...' # encrypted value app: order: timeout-seconds: 30 max-retries: 3

Verify the server is serving config correctly by hitting the endpoint directly:

# GET /{application}/{profile}/{label} curl http://localhost:8888/order-service/prod/main curl http://localhost:8888/order-service/dev # Response includes all resolved property sources in priority order
<!-- pom.xml — Config Client (each microservice) --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> # application.yml — Config Client spring: application: name: order-service # determines which files are fetched from config repo config: import: "optional:configserver:http://localhost:8888" # "optional:" means app starts even if server is down profiles: active: dev cloud: config: fail-fast: true # fail startup if config server unreachable (prod) retry: max-attempts: 6 initial-interval: 1000 multiplier: 1.5 max-interval: 2000 // Properties fetched from Config Server are injected normally @Service public class OrderService { @Value("${app.order.timeout-seconds:10}") private int timeoutSeconds; @Value("${app.order.max-retries:3}") private int maxRetries; } // Or use @ConfigurationProperties for type-safe binding @ConfigurationProperties(prefix = "app.order") @Component public class OrderProperties { private int timeoutSeconds = 10; private int maxRetries = 3; // getters/setters } # Priority order (highest to lowest) for order-service with profile=prod, label=main: # 1. order-service-prod.yml (app + profile specific) # 2. order-service.yml (app specific, all profiles) # 3. application-prod.yml (shared prod overrides) # 4. application.yml (shared defaults) # Labels — serve config from a specific branch or tag spring: cloud: config: label: release/v2.1 # fetch from this Git branch/tag # Config Server also serves native file format (e.g., for feature branches) # Composite backend — Git for prod, filesystem for local dev spring: cloud: config: server: composite: - type: git uri: https://github.com/my-org/config-repo - type: native search-locations: file:./local-config

Without restart, services can reload config by calling the /actuator/refresh endpoint (or using Spring Cloud Bus for fan-out). Beans annotated with @RefreshScope are re-created on refresh.

// Mark the bean as refreshable @RefreshScope @Service public class FeatureFlagService { @Value("${feature.new-checkout-enabled:false}") private boolean newCheckoutEnabled; public boolean isNewCheckoutEnabled() { return newCheckoutEnabled; // re-injected after refresh } } # Trigger a refresh on one service instance curl -X POST http://order-service:8080/actuator/refresh # Fan-out refresh to ALL instances via Spring Cloud Bus (Kafka or RabbitMQ) # Add: spring-cloud-starter-bus-kafka to all services curl -X POST http://config-server:8888/actuator/bus-refresh # Or refresh only services with a specific destination curl -X POST "http://config-server:8888/actuator/bus-refresh/order-service:**" @RefreshScope does NOT work with @ConfigurationProperties beans directly — those need @RefreshScope on the class OR you can use Spring Cloud's auto-rebinding by adding spring-cloud-context which auto-refreshes @ConfigurationProperties beans.

Config Server has built-in encryption/decryption. Store ciphered values in Git — Config Server decrypts them before serving to clients.

# Config Server application.yml — set the encryption key encrypt: key: my-symmetric-key # or use asymmetric RSA key pair # For RSA (more secure): # key-store: # location: classpath:keystore.jks # password: ${KEYSTORE_PASSWORD} # alias: config-server-key # Encrypt a value curl -X POST http://localhost:8888/encrypt -d 'my-secret-password' # Returns: AQABAAEAgBJ3xFq7... # Decrypt (for verification only — clients never call this) curl -X POST http://localhost:8888/decrypt -d 'AQABAAEAgBJ3xFq7...' # Returns: my-secret-password # Store in Git config file with {cipher} prefix spring: datasource: password: '{cipher}AQABAAEAgBJ3xFq7...' # Config Server decrypts this before sending to the client # Client receives the plaintext value For production use, prefer HashiCorp Vault as the Config Server backend instead of Git-based encryption. Spring Cloud Vault integrates natively and provides dynamic secrets, lease rotation, and fine-grained access policies.
# Config Server — add Spring Security HTTP Basic auth # pom.xml: spring-boot-starter-security spring: security: user: name: config-admin password: ${CONFIG_SERVER_PASSWORD} # Config Client — provide credentials spring: cloud: config: uri: http://localhost:8888 username: config-admin password: ${CONFIG_SERVER_PASSWORD} # Production pattern — put Config Server behind service mesh or API gateway # and use mutual TLS or OAuth2 token-based auth instead of HTTP Basic spring: cloud: config: tls: enabled: true key-store: classpath:client-keystore.p12 key-store-password: ${TLS_PASSWORD} trust-store: classpath:truststore.p12 trust-store-password: ${TLS_TRUST_PASSWORD}