Introduction

Spring Cloud Config Server centralizes configuration management by serving properties from Git, SVN, or a database to microservices. When configuration is updated in the Git repository, the running applications do not automatically pick up the changes. The /actuator/refresh endpoint must be called to reload properties, and only beans annotated with @RefreshScope will receive the new values. Without proper setup, configuration changes require full application restarts, defeating the purpose of centralized configuration management.

Symptoms

After updating configuration in Git and calling refresh:

bash
$ curl -X POST http://localhost:8080/actuator/refresh
[]

Empty array means no properties were refreshed. The application continues using old values:

bash
$ curl http://localhost:8080/actuator/env | grep database.url
"database.url": "jdbc:postgresql://old-host:5432/mydb"

Or the refresh endpoint returns 404:

bash
$ curl -X POST http://localhost:8080/actuator/refresh
{"timestamp":"2024-03-15T10:23:45.123+00:00","status":404,"error":"Not Found","path":"/actuator/refresh"}

Common Causes

  • Refresh endpoint not exposed: management.endpoints.web.exposure.include does not include refresh
  • No @RefreshScope on beans: Beans without @RefreshScope do not reload their @Value properties
  • Configuration not from Config Server: Application uses application.yml instead of fetching from Config Server
  • Git webhook not configured: No automatic trigger for refresh after Git push
  • Spring Cloud Bus not configured: Without Spring Cloud Bus, each instance must be refreshed individually
  • @ConfigurationProperties not rebound: Only @RefreshScope beans and @ConfigurationProperties beans are rebound

Step-by-Step Fix

Step 1: Enable the refresh endpoint

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,refresh,env

Step 2: Use @RefreshScope on beans that need runtime updates

```java @RestController @RefreshScope // This bean will be recreated when refresh is called public class ConfigController {

@Value("${database.url}") private String databaseUrl;

@Value("${feature.new-ui.enabled:false}") private boolean newUiEnabled;

@GetMapping("/config") public Map<String, Object> getConfig() { return Map.of( "databaseUrl", databaseUrl, "newUiEnabled", newUiEnabled ); } } ```

Or use @ConfigurationProperties:

java
@Configuration
@ConfigurationProperties(prefix = "database")
@RefreshScope
@Data
public class DatabaseProperties {
    private String url;
    private String username;
    private int maxPoolSize = 10;
}

Step 3: Trigger refresh via actuator

```bash # Refresh all properties curl -X POST http://localhost:8080/actuator/refresh # Returns list of changed keys: ["database.url", "feature.new-ui.enabled"]

# Refresh a specific instance via Spring Cloud Bus curl -X POST http://localhost:8080/actuator/busrefresh ```

Step 4: Automate with Git webhook

```java @RestController public class WebhookController {

private final RestTemplate restTemplate;

public WebhookController(RestTemplate restTemplate) { this.restTemplate = restTemplate; }

@PostMapping("/webhook/config-updated") public void onConfigUpdate(@RequestBody GitWebhookPayload payload) { // Trigger refresh on all instances via Spring Cloud Bus restTemplate.postForEntity( "http://config-server:8888/actuator/busrefresh", null, Void.class ); } } ```

Prevention

  • Always include refresh in management.endpoints.web.exposure.include
  • Use @RefreshScope on beans that contain @Value or @ConfigurationProperties
  • Set up Spring Cloud Bus with RabbitMQ or Kafka for cluster-wide refresh
  • Add a health indicator that reports the current configuration version/commit hash
  • Monitor the /actuator/refresh endpoint response to verify properties are actually reloaded
  • Use Git webhooks to automatically trigger refresh on configuration commits
  • Add integration tests that verify configuration changes are picked up without restart