13 Proven Ways to Dynamically Update SpringBoot Configurations at Runtime
This article presents twelve practical techniques for dynamically modifying SpringBoot configuration values without restarting the application, ranging from simple @Value listeners to advanced solutions such as Nacos, Apollo, dynamic data sources, in‑memory maps, scheduled polling, event‑driven updates, and Spring Cloud Config, helping developers achieve flexible and resilient runtime configuration management.
Dynamic SpringBoot Configuration? 13 Tricks That Actually Work!
Developers often face the problem of hard‑coded configuration values that require a restart to change. This guide shares thirteen practical ways to modify SpringBoot configuration at runtime, ensuring stability, flexibility, and operability.
1. @Value with EnvironmentChangeEvent Listener
Use an @Component that implements ApplicationListener<EnvironmentChangeEvent> to capture changes and re‑inject the new value.
@Value("${timeout:30}")
private String timeout;
@Component
public class TimeoutConfigListener implements ApplicationListener<EnvironmentChangeEvent> {
@Value("${timeout:30}")
private String timeout;
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (event.getKeys().contains("timeout")) {
String newVal = ApplicationContextProvider.getContext()
.getEnvironment().getProperty("timeout");
timeout = newVal;
System.out.println("Timeout updated: " + timeout);
}
}
}Helper class to obtain the ApplicationContext:
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}
public static ApplicationContext getContext() {
return ctx;
}
}2. Nacos with @RefreshScope
Integrate Spring Cloud Nacos; after changing a property in Nacos, the bean refreshes automatically.
@RefreshScope
@RestController
public class BizController {
@Value("${biz.feature.switch:false}")
private boolean featureSwitch;
@GetMapping("/feature/status")
public String checkFeature() {
return "Current feature switch: " + featureSwitch;
}
}Bootstrap configuration (bootstrap.yml):
spring:
application:
name: config-demo
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml3. Apollo
Apollo provides centralized configuration with real‑time updates and advanced features such as gray releases.
@RestController
public class ApolloController {
@ApolloConfig
private Config config;
@GetMapping("/current/env")
public String getEnv() {
String env = config.getProperty("app.env", "dev");
return "Current environment: " + env;
}
}4. Dynamic DataSource Switching
Register multiple data sources and switch at runtime using a ThreadLocal holder and a custom AbstractRoutingDataSource.
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String key) { contextHolder.set(key); }
public static String getDataSource() { return contextHolder.get(); }
public static void clearDataSource() { contextHolder.remove(); }
} public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSource();
}
}Usage in a controller:
DynamicDataSourceContextHolder.setDataSource("tenantA");
// execute queries
DynamicDataSourceContextHolder.clearDataSource();5. Runtime Bean Property Modification
Expose an endpoint that obtains the bean from the ApplicationContext and updates its fields via a setter.
@Service
public class BizService {
private int retryCount = 3;
public void doSomething() {
System.out.println("Current retry count: " + retryCount);
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
} @PostMapping("/set-retry")
public String setRetry(@RequestParam int count) {
BizService bean = ctx.getBean(BizService.class);
bean.setRetryCount(count);
return "Retry count set to: " + count;
}6. In‑Memory Map Configuration
Store key‑value pairs in a concurrent map and provide simple CRUD endpoints.
@Component
public class InMemoryConfig {
private final Map<String, String> configMap = new ConcurrentHashMap<>();
public String get(String key) { return configMap.getOrDefault(key, ""); }
public void set(String key, String val) { configMap.put(key, val); }
} @PostMapping("/update-config")
public String update(@RequestParam String key, @RequestParam String value) {
config.set(key, value);
return "Update successful";
}7. Spring Cloud Config
Centralize configuration in a Git repository; services pull the latest values and refresh with @RefreshScope and the /actuator/refresh endpoint.
# Config server (application.yml)
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://gitee.com/your-team/config-repo.git # Client (bootstrap.yml)
spring:
application:
name: your-app
cloud:
config:
uri: http://localhost:8888 @RefreshScope
@RestController
public class ConfigClientController {
@Value("${biz.name:default}")
private String bizName;
@GetMapping("/get-biz")
public String biz() {
return "Current biz name: " + bizName;
}
}8. Scheduled Polling
Periodically fetch configuration from Redis or a database and update a local cache.
@Component
public class ConfigPuller {
private final Map<String, String> localCache = new ConcurrentHashMap<>();
@Scheduled(fixedRate = 10000)
public void refreshConfig() {
String val = redisTemplate.opsForValue().get("sys.timeout");
if (val != null) {
localCache.put("sys.timeout", val);
System.out.println("Config refreshed, timeout: " + val);
}
}
public String get(String key) {
return localCache.getOrDefault(key, "");
}
}9. Event‑Driven Config Refresh
Publish a custom ConfigUpdateEvent after a config change; interested components listen and react.
public class ConfigUpdateEvent extends ApplicationEvent {
private final String key;
private final String value;
public ConfigUpdateEvent(Object source, String key, String value) {
super(source);
this.key = key;
this.value = value;
}
// getters omitted
} @Component
public class BizModule implements ApplicationListener<ConfigUpdateEvent> {
@Override
public void onApplicationEvent(ConfigUpdateEvent event) {
if ("biz.switch".equals(event.getKey())) {
System.out.println("Biz switch updated to: " + event.getValue());
}
}
} @PostMapping("/update")
public String update(@RequestParam String key, @RequestParam String val) {
// update local cache …
publisher.publishEvent(new ConfigUpdateEvent(this, key, val));
return "Config published";
}10. Environment Hack
Directly inject a new property source at the highest precedence.
@Autowired
private ConfigurableEnvironment environment;
public void updateProperty(String key, String value) {
MutablePropertySources propSources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put(key, value);
propSources.addFirst(new MapPropertySource("customOverride", map));
}11. @ConfigurationProperties with RefreshScope
Bind a POJO to a configuration prefix and modify its fields at runtime.
@Component
@ConfigurationProperties(prefix = "biz.config")
@RefreshScope
public class BizProperties {
private int threadCount;
private boolean enable;
// getters and setters
} @PostMapping("/set-thread")
public String update(@RequestParam int count) {
props.setThreadCount(count);
return "Thread count set to: " + count;
}12. Zookeeper Listener
Subscribe to ZK node changes; the callback updates local cache instantly.
zkClient.subscribeDataChanges("/config/timeout", new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) {
System.out.println("Config changed: " + dataPath + " = " + data);
// update local cache
}
@Override
public void handleDataDeleted(String dataPath) {
System.out.println("Config deleted: " + dataPath);
}
});These twelve (actually thirteen) techniques cover lightweight local solutions, centralized configuration platforms, and extreme runtime hacks, giving developers a toolbox to keep SpringBoot applications flexible without costly restarts.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
