See https://github.com/spring-projects/spring-boot/issues/21542 for background.
Currently a PropertySource
can legitimately contain null
values. For example, a MapPropertySource
can return true
from containsProperty
and null
from getProperty
if the backing Map
has an entry with a null value.
The PropertySourcesPropertyResolver
class currently treats null results as missing properties. This makes if impossible for a higher PropertySource
to override a lower one with a null
value.
The resolver could call containsProperty
to check for a value and allow null
results, however, this has some downsides:
- It's less efficient since two calls need to be made for each matching value
- It potentially breaks back-compatibility
I've raised this issue to discuss if the design is intentional or not.
Comment From: jhoeller
This is intentional as far as I see, with PropertySource.getProperty
documented as "Return the value associated with the given name, or null if not found". From that perspective, potential misbehavior is rather on MapPropertySource
not handling containsProperty
correctly.
Comment From: jhoeller
On review, I'd rather let MapPropertySource
perform its efficient containsProperty
call, just documenting that the given Map
must not contain null
values (either by using a corresponding implementation or by simply not populating it with null values) in order to get consistent getProperty
vs containsProperty
behavior.
Comment From: jhoeller
Reading through the Boot issue, maybe affected PropertySource
implementations could explicitly turn user-declared null
values into empty Strings?
Comment From: rgordeev
@jhoeller
But the problem concerns not only null values for strings, but empty values for lists or maps - it's impossible to override values from application.yaml
with SPRING_APPLICATION_JSON
, that contains empty values or null
s.
E.g.
application:
a: some string
b:
- 1
- 2
If I try to override b
property with
SPRING_APPLICATION_JSON={"application": {"b": null}}
or
SPRING_APPLICATION_JSON={"application": {"b": {}}}
or
SPRING_APPLICATION_JSON={"application": {"b": []}}
nothing happens, application will get
application:
a: some string
b:
- 1
- 2
in every mentioned case.
Comment From: philwebb
@rgordeev I missed those from the original report. I've reopened the Spring Boot issue.
Comment From: philwebb
Thanks @jhoeller, I'll reopen the Boot issue and update this one as documentation for MapPropertySource
.
Comment From: rgordeev
Sometimes results of overriding are not obvious at all. You may see test cases Tests
Test case Defaults
application-test.yaml
application:
a: "some string"
b:
- 1
- 2
- 3
c:
- "one"
- "two"
- "three"
m:
- one: 1
- two: 2
Expecting Actual
a == "" a == ""
b == [1,2,3] b == [1,2,3]
c == ["one","two","three"] c == ["one","two","three"]
m == {"one":1, "two":2} m == {"one":1, "two":2}
Test case With Empty Lists
application-test.yaml
application:
a: "some string"
b:
- 1
- 2
- 3
c:
- "one"
- "two"
- "three"
m:
- one: 1
- two: 2
We are trying to override - string property with null, - list properties with empty json array [], - map with empty json object {}
SPRING_APPLICATION_JSON = {
"application": {
"a": null,
"b": [],
"c": [],
"m": {}
}
}
Expecting Actual
a == null a == "some string"
b == null | empty array list b == [1,2,3]
c == null | empty array list c == ["one","two","three"]
m == null | empty map m == {"one":1, "two":2}
Test case With Empty Objects
application-test.yaml
application:
a: "some string"
b:
- 1
- 2
- 3
c:
- "one"
- "two"
- "three"
m:
- one: 1
- two: 2
We are trying to override - string property with null, - list properties with empty object {}, - map with empty json object {}
SPRING_APPLICATION_JSON = {
"application": {
"a": null,
"b": {},
"c": {},
"m": {}
}
}
Expecting Actual
a == null a == "some string"
b == null | empty array list b == [1,2,3]
c == null | empty array list c == ["one","two","three"]
m == null | empty map m == {"one":1, "two":2}
Test case With Empty Strings
application-test.yaml
application:
a: "some string"
b:
- 1
- 2
- 3
c:
- "one"
- "two"
- "three"
m:
- one: 1
- two: 2
We are trying to override - string property with empty string "", - list properties with empty string "", - map properties with empty string ""
SPRING_APPLICATION_JSON = {
"application": {
"a": "",
"b": "",
"c": "",
"m": ""
}
}
Expecting Actual
a == "" a == ""
b == null | empty array list b == []
c == null | empty array list c == []
m == null | empty map m == {}
Test case With Non null Values
application-test.yaml
application:
a: "some string"
b:
- 1
- 2
- 3
c:
- "one"
- "two"
- "three"
m:
- one: 1
- two: 2
We are trying to override - string property with empty string "", - list properties with non empty json arrays [9, 10] and ["ten", "nine"], - map properties with non empty json object {"nine": 9}
SPRING_APPLICATION_JSON = {
"application": {
"a": "",
"b": [9,10],
"c": ["ten", "nine"],
"m": {"nine": 9}
}
}
Expecting Actual
a == "" a == ""
b == [9,10] b == [9,10]
c == ["ten","nine"] c == ["ten","nine"]
m == {"nine": 9} m == {"one":1, "two":2, "nine":9}
Test case With Null values
application-test.yaml
application:
a: "some string"
b:
- 1
- 2
- 3
c:
- "one"
- "two"
- "three"
m:
- one: 1
- two: 2
We are trying to override - string property with null, - list properties with null, - map properties with null
SPRING_APPLICATION_JSON = {
"application": {
"a": null,
"b": null,
"c": null,
"m": null
}
}
Expecting Actual
a == "" a == "some string"
b == null | empty b == [1,2,3]
c == null | empty c == ["one","two","three"]
m == null | empty m == {"one":1, "two":2}
Comment From: rgordeev
E.g.
* you may override every non empty property with empty string ""
regardless the type of properties (ordinal or collection)
* if you try to override map property with non empty values it adds value to existing ones, not overrides them
* if you try to override property with null
or []
or {}
nothing happens