Introduction

Spring Cloud Config enables centralized configuration management with runtime refresh via /actuator/refresh. However, beans do not pick up new configuration values when they are not annotated with @RefreshScope, when the refresh endpoint is not exposed, or when configuration properties are bound at startup and never rebound. The @RefreshScope proxy mechanism creates a new bean instance on next access after a refresh event, but beans without this annotation retain their original values. Additionally, @Value injection and @ConfigurationProperties behave differently during refresh.

Symptoms

bash
# Config server updated, refresh called, but values unchanged
curl -X POST http://localhost:8080/actuator/refresh
# Returns: []  (empty - no beans refreshed)

Or:

json
{
    "timestamp": "2026-04-09T10:00:00.000+00:00",
    "status": 404,
    "error": "Not Found",
    "path": "/actuator/refresh"
}

Common Causes

  • @RefreshScope missing: Bean not annotated for refresh
  • Refresh endpoint not exposed: management.endpoints.web.exposure missing refresh
  • @Value used instead of @ConfigurationProperties: @Value binds once at startup
  • Bean initialized before refresh: Singleton bean cached values at startup
  • Refresh event not published: Config client not connected to config server
  • EnvironmentChangedEvent not processed: Custom event listener not triggered

Step-by-Step Fix

Step 1: Use @RefreshScope on beans that need refresh

```java import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Service;

@Service @RefreshScope // Bean will be recreated on refresh event public class NotificationService {

@Value("${notification.retry.count:3}") private int retryCount;

@Value("${notification.timeout:5000}") private int timeout;

public void send(String message) { // Uses current values after refresh for (int i = 0; i < retryCount; i++) { try { doSend(message, timeout); break; } catch (Exception e) { // retry } } } } ```

Step 2: Expose refresh endpoint

```yaml # application.yml management: endpoints: web: exposure: include: health,info,refresh,env # Include refresh endpoint

# Spring Cloud Config client spring: config: import: optional:configserver:http://config-server:8888 cloud: config: uri: http://config-server:8888 fail-fast: true retry: initial-interval: 1000 max-interval: 5000 max-attempts: 6 ```

Step 3: Use @ConfigurationProperties with refresh

```java import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component;

@Component @RefreshScope @ConfigurationProperties(prefix = "app") @Data // Lombok public class AppProperties { private String apiEndpoint; private int maxRetries = 3; private Duration timeout = Duration.ofSeconds(5); }

// Usage @Service public class ApiService { private final AppProperties props;

public ApiService(AppProperties props) { this.props = props; // Injected proxy - values update on refresh } } ```

Prevention

  • Add @RefreshScope to every bean that reads dynamic configuration
  • Expose /actuator/refresh endpoint in management configuration
  • Prefer @ConfigurationProperties over @Value for type-safe, refreshable config
  • Log EnvironmentChangeEvent to track which properties changed
  • Test refresh behavior by modifying config and calling /actuator/refresh
  • Use spring-cloud-starter-config for automatic config server integration
  • Add config server health check to verify connectivity on startup