Spring Boot
Config Server
Spring Cloud Config Server provides centralised external configuration for distributed systems. It serves configuration from a Git repository, filesystem, or Vault to any number of client applications. Clients fetch their configuration on startup and optionally refresh it at runtime without restarting. This entry covers server setup, Git backend, encryption, client configuration, refresh scope, and security.
Config Server Setup
Create a dedicated Spring Boot application for the config server. Add @EnableConfigServer and spring-cloud-config-server. The server reads configuration files from a Git repository and serves them to clients over HTTP. The repository can be a remote Git host or a local filesystem for development.
XML
<!-- Config Server pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
// ── Main application class ────────────────────────────────────────────
@SpringBootApplication
@EnableConfigServer
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/myorg/config-repo
default-label: main # branch
search-paths:
- '{application}' # per-app subdirectory
- '{application}/{profile}' # per-app per-profile
clone-on-start: true # fail fast on startup
timeout: 10
force-pull: true # always pull latest
# Credentials for private repos
username: ${GIT_USERNAME}
password: ${GIT_TOKEN} # personal access token
# Secure the config server itself
security:
user:
name: ${CONFIG_SERVER_USER:config}
password: ${CONFIG_SERVER_PASSWORD}
# ── Config repository file structure ──────────────────────────────────
# config-repo/
# ├── application.yml # shared by ALL services
# ├── application-prod.yml # shared prod overrides
# ├── order-service/
# │ ├── order-service.yml # order-service defaults
# │ └── order-service-prod.yml # order-service prod overrides
# └── user-service/
# ├── user-service.yml
# └── user-service-prod.ymlGit Backend Configuration
The Git backend supports multiple repositories, pattern-matching to route applications to different repos, and SSH key authentication for private repositories. For monorepo setups, use search-paths to organise configuration files by application name.
yaml
# ── Multiple repositories (route by application name) ────────────────
spring:
cloud:
config:
server:
git:
uri: https://github.com/myorg/default-config
repos:
# Payment service uses a dedicated secure repo
payment-config:
pattern: payment-service*
uri: https://github.com/myorg/payment-config
username: ${GIT_USERNAME}
password: ${GIT_TOKEN}
# All services ending in -service use the services repo
services-config:
pattern: '*-service*'
uri: https://github.com/myorg/services-config
clone-on-start: true
# ── SSH authentication ─────────────────────────────────────────────────
spring:
cloud:
config:
server:
git:
uri: git@github.com:myorg/config-repo.git
ignore-local-ssh-settings: true
private-key: |
-----BEGIN RSA PRIVATE KEY-----
${GIT_SSH_PRIVATE_KEY}
-----END RSA PRIVATE KEY-----
known-hosts-file: /config/known_hosts
# ── Local filesystem backend (development) ────────────────────────────
spring:
cloud:
config:
server:
native:
search-locations:
- file:./config-repo
- classpath:/config-repo
# ── Config server endpoints ───────────────────────────────────────────
# GET /{application}/{profile}[/{label}]
# GET /{application}-{profile}.yml
# GET /{application}-{profile}.properties
#
# Examples:
# GET /order-service/prod/main
# → merges: application.yml + application-prod.yml
# + order-service.yml + order-service-prod.yml
#
# GET /order-service/default
# → merges: application.yml + order-service.ymlEncrypting Sensitive Properties
Store passwords, API keys, and secrets encrypted in the Git repository using Spring Cloud Config's symmetric or asymmetric encryption. The config server decrypts values automatically before serving them to clients. Values are prefixed with {cipher} in the repository.
yaml
# ── Symmetric encryption (single key) ────────────────────────────────
# application.yml (Config Server)
encrypt:
key: ${ENCRYPT_KEY} # 32+ char random string in secrets manager
# ── Asymmetric encryption (RSA key pair) — more secure ────────────────
encrypt:
key-store:
location: classpath:/keystore.jks
password: ${KEYSTORE_PASSWORD}
alias: configkey
secret: ${KEY_PASSWORD}
# ── Generate a keystore ────────────────────────────────────────────────
# keytool -genkeypair -alias configkey -keyalg RSA # -dname "CN=Config Server,O=MyOrg" # -keystore keystore.jks -storepass changeme
// ── Encrypt a value using the config server endpoint ──────────────────
// POST http://localhost:8888/encrypt
// Body: myDatabasePassword
// Response: AQB7xK9...encrypted...
// ── Store in Git repository ───────────────────────────────────────────
// order-service.yml (in Git repo):
// spring:
// datasource:
// password: '{cipher}AQB7xK9...encrypted...'
// redis:
// password: '{cipher}BQC8yL0...encrypted...'
// ── Config server serves decrypted values to clients ──────────────────
// Client receives: spring.datasource.password=myDatabasePassword
// ── Prevent decryption at server (decrypt on client instead) ──────────
# Config server application.yml:
spring:
cloud:
config:
server:
encrypt:
enabled: false # serve {cipher} as-is
# Client must then configure its own encryption key
// ── Health check — verify encryption is working ───────────────────────
@Component
@RequiredArgsConstructor
@Slf4j
public class EncryptionHealthCheck {
private final TextEncryptor encryptor;
@PostConstruct
public void verify() {
String test = "health-check-value";
String encrypted = encryptor.encrypt(test);
String decrypted = encryptor.decrypt(encrypted);
if (!test.equals(decrypted)) {
throw new IllegalStateException(
"Config server encryption is not working");
}
log.info("Config server encryption verified");
}
}Config Client Setup
Client services fetch configuration from the config server on startup. Add spring-cloud-starter-config and configure the server URI in bootstrap.yml or application.yml. The client merges remote configuration with local configuration — remote values take precedence by default.
XML
<!-- Client service pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# ── application.yml (client service) ──────────────────────────────────
spring:
application:
name: order-service # determines which config files to fetch
profiles:
active: ${SPRING_PROFILES_ACTIVE:default}
config:
import: "configserver:" # Spring Boot 2.4+ style
cloud:
config:
uri: http://config-server:8888
username: ${CONFIG_SERVER_USER:config}
password: ${CONFIG_SERVER_PASSWORD}
label: main # Git branch
fail-fast: true # fail on startup if server unreachable
retry:
initial-interval: 1000
max-attempts: 6
max-interval: 2000
multiplier: 1.1
# ── Actuator — expose refresh endpoint ────────────────────────────────
management:
endpoints:
web:
exposure:
include: health,info,refresh,env,configprops
endpoint:
refresh:
enabled: true
// ── Verify configuration loaded from server ───────────────────────────
@RestController
@RequestMapping("/api/v1/config")
@Slf4j
public class ConfigVerificationController {
@Value("${app.feature.newCheckout:false}")
private boolean newCheckoutEnabled;
@Value("${app.external.paymentGateway.url}")
private String paymentGatewayUrl;
@GetMapping("/properties")
@PreAuthorize("hasRole('ADMIN')")
public Map<String, Object> properties() {
return Map.of(
"newCheckoutEnabled", newCheckoutEnabled,
"paymentGatewayUrl", paymentGatewayUrl
);
}
}@RefreshScope — Runtime Configuration Updates
@RefreshScope marks beans whose properties should be re-injected when the /actuator/refresh endpoint is called, without restarting the application. The bean is destroyed and recreated with the latest configuration values. Combine with Spring Cloud Bus to broadcast refreshes to all instances simultaneously.
XML
// ── RefreshScope bean — re-created on refresh ────────────────────────
@Service
@RefreshScope
@RequiredArgsConstructor
@Slf4j
public class FeatureFlagService {
@Value("${app.features.newCheckout:false}")
private boolean newCheckoutEnabled;
@Value("${app.features.maxOrderItems:50}")
private int maxOrderItems;
@Value("${app.features.promoCode:}")
private String activePromoCode;
public boolean isNewCheckoutEnabled() {
return newCheckoutEnabled;
}
public int getMaxOrderItems() {
return maxOrderItems;
}
}
// ── Configuration properties with RefreshScope ────────────────────────
@ConfigurationProperties(prefix = "app.payments")
@RefreshScope
@Getter @Setter
public class PaymentConfig {
private String gatewayUrl;
private String apiKey;
private int timeoutSeconds = 30;
private boolean sandboxMode = false;
}
// ── Trigger refresh manually ──────────────────────────────────────────
// POST http://order-service:8080/actuator/refresh
// Response: ["app.features.newCheckout","app.features.maxOrderItems"]
// (list of changed properties)
// ── Spring Cloud Bus — broadcast refresh to ALL instances ─────────────
<!-- pom.xml — add to config server AND all clients -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
<!-- or spring-cloud-starter-bus-kafka -->
</dependency>
# application.yml (config server + clients)
spring:
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: 5672
username: ${RABBITMQ_USER}
password: ${RABBITMQ_PASS}
# One call to the bus refresh endpoint notifies ALL instances:
# POST http://config-server:8888/actuator/busrefresh
# → All registered clients refresh simultaneously via RabbitMQ
// ── Git webhook → automatic refresh ───────────────────────────────────
// GitHub/GitLab webhook → POST /monitor on the config server
// → Config server publishes RefreshRemoteApplicationEvent
// → All clients via Spring Cloud Bus refresh their config
// No manual curl needed — config updates propagate automaticallyConfig Server Security
Secure the config server with HTTP Basic authentication, mutual TLS, or by placing it behind an API gateway accessible only within the service mesh. Clients must authenticate on every request. For production, use Vault as the backend to leverage dynamic secrets, audit logs, and fine-grained access policies.
Java
// ── Config server security configuration ─────────────────────────────
@Configuration
@EnableWebSecurity
public class ConfigServerSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
// Health check public — Kubernetes probes need this
.requestMatchers("/actuator/health").permitAll()
// Encrypt/decrypt endpoints — admin only
.requestMatchers("/encrypt", "/decrypt")
.hasRole("ADMIN")
// All config endpoints require authentication
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.build();
}
@Bean
public UserDetailsService users(PasswordEncoder enc) {
UserDetails configClient = User.withUsername("config")
.password(enc.encode("${CONFIG_CLIENT_PASSWORD}"))
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password(enc.encode("${CONFIG_ADMIN_PASSWORD}"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(
configClient, admin);
}
}
# ── HashiCorp Vault backend ────────────────────────────────────────────
spring:
cloud:
config:
server:
vault:
host: vault.internal
port: 8200
scheme: https
authentication: TOKEN
token: ${VAULT_TOKEN}
kv-version: 2
default-key-value-backend: secret
# Vault stores secrets at:
# secret/application → shared by all services
# secret/order-service → order-service specific
# secret/order-service/prod → order-service prod overrides
// ── Actuator security — never expose /env in production ───────────────
management:
endpoints:
web:
exposure:
include: health,info,refresh
# NEVER expose env or configprops in production
# — they print all property values including secrets