ConfigurationProperties initial values not restored on DELETE /actuator/env
Describe the bug
We are setting a configuration value via POST /actuator/env and later attempt to restore the original value by running DELETE /actuator/env.
However, this does not work — Spring does not restore the values defined in the field declaration.
Sample
@ConfigurationProperties("my-properties")
class MyProperties {
private String name = "initial";
}
Change the value via actuator and delete the change:
curl -H 'Content-Type: application/json' --data '{"name": "my-properties.name", "value": "from-actuator"}' -X POST http://localhost:8080/actuator/env
curl -X DELETE http://localhost:8080/actuator/env
After this, the value is still from-env although I would have expected initial again.
It seems likeJavaBeanBinder is not able to do that. (This is even tested here)
Thanks for reporting this issue, @david0. After investigating, I believe this is an expected behavior based on how Spring Boot's property binding works.
The issue is in Spring Boot's Binder class, not in Spring Cloud Config or Spring Cloud Commons.
When you call DELETE /actuator/env:
- The
"manager"property source is correctly removed from the Environment ConfigurationPropertiesRebindercorrectly triggers rebinding of your@ConfigurationPropertiesbeans- However, the
Binderuses Java default values (null, 0, false) when properties are absent - it never uses field initializers
Why Field Initializers Aren't Restored
Field initializers like this:
@ConfigurationProperties("my-properties")
class MyProperties {
private String name = "initial"; // Field initializer
}
Are only executed during object construction. During rebinding:
- The bean is not recreated from scratch
- Spring Boot only re-sets property values
- The
Binderhas no access to field initializer values from your source code - When a property is absent, it uses Java defaults (null for objects, 0 for primitives)
While I agree that your analysis is correct I do not understand why it is accepted that this is "just the way how it is".
Its counter-intuitive and makes the use of field initialisers for @ConfigurationProperties questionable, as they will not handled correctly. At least that should be documented.
I also think it does not has to be this way, as a fresh bean could be created and the values could be copied over. Theoretically that would work and should at least be considered.
It is not something Spring Cloud can address since it is behavior of the Binder in Spring Boot so if the behavior needs to change, the change needs to be made in Spring Boot
I don't think Boot can do anything about this. Property rebinding is a Spring Cloud feature that Boot knows nothing about.
During rebinding:
The bean is not recreated from scratch
This is the crux of the problem. AFAIK, there's no way to replay a class's field initialisers on an existing instance. As a result, for rebinding to work reliably, the instance has to be recreated. For that to work, everywhere that the @ConfigurationProperties bean has been injected a proxy is required. Perhaps @RefreshScope can help here? Alternatively, perhaps Cloud can allow people to opt in to proxying of all @ConfigurationProperties beans when someone wants to use rebinding and then recreate the instance that's behind the proxy upon an EnvironmentChangedEvent.
Thanks for the additional thoughts @wilkinsona.
I will reopen this issue, but to be honest it is not going to be a high priority for us at this time as we have a lot on our plate at the moment.
I will add the help wanted label here to see if anyone in the community wants to take a look at this.