Persistent @ConfigurationProperties with EnvironmentPostProcessor in Spring Boot
Configuration properties is a great feature in Spring Boot. In a monolithic long-time running application, we often want to change the application settings. Those changes must be persisted to survive the application restart.
We can use environment post-processing for that. This post describes how.

Let’s start slowly; consider a simple class with configuration properties:
@ConfigurationProperties("hello")
@Getter
@Setter
class HelloConfigurationProperties { private String greeting = "Hello!";
}
The property of greeting is used to print a greeting message from a REST controller:
@RestController
@RequestMapping("/hello")
@AllArgsConstructor
class HelloController { private String greeting; @GetMapping
public String sayHello() {
return this.greeting;
}
}
After building and starting the application, we can see the default greeting to be printed:
$ curl http://localhost/hello
> Hello!
The message can be changed by setting an environment variable:
HELLO_GREETING=Hi!// restart the application...$ curl http://localhost/hello
> Hi!
As our application can’t be restarted easily (there are such cases!), we introduce a new endpoint to update the greeting message:
// ...
class HelloController {
// ... @PutMapping
public ResponseEntity updateGreeting(String greeting) {
this.greeting = greeting;
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}$ curl http://localhost/hello?greeting=Ahoy! -X PUT
$ curl http://localhost/hello
> Ahoy!
Now it’s time to think about persistence. We introduce a class persisting properties into a database:
@RequiredArgsConstructor
public class Properties { private final JdbcTemplate jdbcTemplate; public void store(String name, Object value) {
jdbcTemplate.update(
"UPDATE app_properties SET value = ? WHERE name = ?",
value, name);
} public Set all() {
return jdbcTemplate.queryForList(
"SELECT name, value FROM app_properties").stream()
.map(m -> new Property((String)m.get("name"),
(String)m.get("value")))
.collect(Collectors.toSet());
}
}
And use it in the controller:
// ...
class HelloController { private final Properties properties; // ... @PutMapping
public ResponseEntity updateGreeting(String greeting) {
this.greeting = greeting;
this.properties.store("hello.greeting", greeting); return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
Now we have the changes in the database. The last step is to fill the configuration properties at the startup. It’s where the environment post-processor comes to shine:
class ConfigurationPropertiesFromDatabasePostProcessor
implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(
ConfigurableEnvironment env, SpringApplication app) {
try {
DataSource dataSource = DataSourceBuilder
.create()
.url(env.getProperty("spring.datasource.url"))
.username(env.getProperty("spring.datasource.username"))
.password(env.getProperty("spring.datasource.password"))
.driverClassName(env.getProperty("spring.datasource.driver-class-name"))
.build(); env.getPropertySources().addLast(
new MapPropertySource(
"propertiesFromDatabase",
new Properties(new JdbcTemplate(dataSource))
.all().stream()
.collect(Collectors.toMap(
Property::getName, Property::getValue)))); } catch (BadSqlGrammarException ignore) {
} catch (Exception e) {
throw new RuntimeException(e);
}
} @Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
Probably the biggest gotcha is the need to create a new data-source. As the environment post-processor runs before application context is refreshed, there are no ready-to-use beans so far. We have to create everything we need from scratch.
Properties from the database are loaded and put as a new property-source with the lowest priority — this means, we can still overwrite them using environment variables, system properties etc. If this behavior is not desired, we can change the order by using addFirst
, addBefore
or addAfter
.
We have to add the post-processor entry into META-INF/spring.factories
:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ttulka.ConfigurationPropertiesFromDatabasePostProcessor
That’s it! Our configuration changes now survive a restart of the application.
You can find a working example on my GitHub.
The idea is also implemented in this small Spring Boot tool: