Skip to content

Commit

Permalink
Document safe navigation semantics within compound expressions in SpEL
Browse files Browse the repository at this point in the history
Closes gh-21827
  • Loading branch information
sbrannen committed Feb 11, 2024
1 parent 4a5dc7c commit 4a3ef3e
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ The safe navigation operator (`?`) is used to avoid a `NullPointerException` and
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
language. Typically, when you have a reference to an object, you might need to verify
that it is not `null` before accessing methods or properties of the object. To avoid
this, the safe navigation operator returns `null` instead of throwing an exception.
this, the safe navigation operator returns `null` for the particular null-safe operation
instead of throwing an exception.

[WARNING]
====
When the safe navigation operator evaluates to `null` for a particular null-safe
operation within a compound expression, the remainder of the compound expression will
still be evaluated.
See <<expressions-operator-safe-navigation-compound-expressions>> for details.
====

[[expressions-operator-safe-navigation-property-access]]
== Safe Property and Method Access
Expand Down Expand Up @@ -283,3 +293,71 @@ Kotlin::
======


[[expressions-operator-safe-navigation-compound-expressions]]
== Null-safe Operations in Compound Expressions

As mentioned at the beginning of this section, when the safe navigation operator
evaluates to `null` for a particular null-safe operation within a compound expression,
the remainder of the compound expression will still be evaluated. This means that the
safe navigation operator must be applied throughout a compound expression in order to
avoid any unwanted `NullPointerException`.

Given the expression `#person?.address.city`, if `#person` is `null` the safe navigation
operator (`?.`) ensures that no exception will be thrown when attempting to access the
`address` property of `#person`. However, since `#person?.address` evaluates to `null`, a
`NullPointerException` will be thrown when attempting to access the `city` property of
`null`. To address that, you can apply null-safe navigation throughout the compound
expression as in `#person?.address?.city`. That expression will safely evaluate to `null`
if either `#person` or `#person?.address` evaluates to `null`.

The following example demonstrates how to use the "null-safe select first" operator
(`?.^`) on a collection combined with null-safe property access (`?.`) within a compound
expression. If `members` is `null`, the result of the "null-safe select first" operator
(`members?.^[nationality == 'Serbian']`) evaluates to `null`, and the additional use of
the safe navigation operator (`?.name`) ensures that the entire compound expression
evaluates to `null` instead of throwing an exception.

[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.^[nationality == 'Serbian']?.name"; // <1>
// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
.getValue(context, String.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
.getValue(context, String.class);
----
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression = "members?.^[nationality == 'Serbian']?.name" // <1>
// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
.getValue(context, String::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
.getValue(context, String::class.java)
----
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
======
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,25 @@ void nullSafeSelectFirst() {
assertThat(inventor).isNull();
}

@Test
void nullSafeSelectFirstAndPropertyAccess() {
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.^[nationality == 'Serbian']?.name"; // <1>

// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
.getValue(context, String.class);
assertThat(name).isEqualTo("Nikola Tesla");

society.members = null;

// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
.getValue(context, String.class);
assertThat(name).isNull();
}

@Test
@SuppressWarnings("unchecked")
void nullSafeSelectLast() {
Expand Down

0 comments on commit 4a3ef3e

Please sign in to comment.