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 nulls. 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