Skip to content

Commit 4a3ef3e

Browse files
committed
Document safe navigation semantics within compound expressions in SpEL
Closes gh-21827
1 parent 4a5dc7c commit 4a3ef3e

File tree

2 files changed

+98
-1
lines changed

2 files changed

+98
-1
lines changed

framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@ The safe navigation operator (`?`) is used to avoid a `NullPointerException` and
55
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
66
language. Typically, when you have a reference to an object, you might need to verify
77
that it is not `null` before accessing methods or properties of the object. To avoid
8-
this, the safe navigation operator returns `null` instead of throwing an exception.
8+
this, the safe navigation operator returns `null` for the particular null-safe operation
9+
instead of throwing an exception.
10+
11+
[WARNING]
12+
====
13+
When the safe navigation operator evaluates to `null` for a particular null-safe
14+
operation within a compound expression, the remainder of the compound expression will
15+
still be evaluated.
16+
17+
See <<expressions-operator-safe-navigation-compound-expressions>> for details.
18+
====
919

1020
[[expressions-operator-safe-navigation-property-access]]
1121
== Safe Property and Method Access
@@ -283,3 +293,71 @@ Kotlin::
283293
======
284294

285295

296+
[[expressions-operator-safe-navigation-compound-expressions]]
297+
== Null-safe Operations in Compound Expressions
298+
299+
As mentioned at the beginning of this section, when the safe navigation operator
300+
evaluates to `null` for a particular null-safe operation within a compound expression,
301+
the remainder of the compound expression will still be evaluated. This means that the
302+
safe navigation operator must be applied throughout a compound expression in order to
303+
avoid any unwanted `NullPointerException`.
304+
305+
Given the expression `#person?.address.city`, if `#person` is `null` the safe navigation
306+
operator (`?.`) ensures that no exception will be thrown when attempting to access the
307+
`address` property of `#person`. However, since `#person?.address` evaluates to `null`, a
308+
`NullPointerException` will be thrown when attempting to access the `city` property of
309+
`null`. To address that, you can apply null-safe navigation throughout the compound
310+
expression as in `#person?.address?.city`. That expression will safely evaluate to `null`
311+
if either `#person` or `#person?.address` evaluates to `null`.
312+
313+
The following example demonstrates how to use the "null-safe select first" operator
314+
(`?.^`) on a collection combined with null-safe property access (`?.`) within a compound
315+
expression. If `members` is `null`, the result of the "null-safe select first" operator
316+
(`members?.^[nationality == 'Serbian']`) evaluates to `null`, and the additional use of
317+
the safe navigation operator (`?.name`) ensures that the entire compound expression
318+
evaluates to `null` instead of throwing an exception.
319+
320+
[tabs]
321+
======
322+
Java::
323+
+
324+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
325+
----
326+
ExpressionParser parser = new SpelExpressionParser();
327+
IEEE society = new IEEE();
328+
StandardEvaluationContext context = new StandardEvaluationContext(society);
329+
String expression = "members?.^[nationality == 'Serbian']?.name"; // <1>
330+
331+
// evaluates to "Nikola Tesla"
332+
String name = parser.parseExpression(expression)
333+
.getValue(context, String.class);
334+
335+
society.members = null;
336+
337+
// evaluates to null - does not throw a NullPointerException
338+
name = parser.parseExpression(expression)
339+
.getValue(context, String.class);
340+
----
341+
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
342+
343+
Kotlin::
344+
+
345+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
346+
----
347+
val parser = SpelExpressionParser()
348+
val society = IEEE()
349+
val context = StandardEvaluationContext(society)
350+
val expression = "members?.^[nationality == 'Serbian']?.name" // <1>
351+
352+
// evaluates to "Nikola Tesla"
353+
String name = parser.parseExpression(expression)
354+
.getValue(context, String::class.java)
355+
356+
society.members = null
357+
358+
// evaluates to null - does not throw a NullPointerException
359+
name = parser.parseExpression(expression)
360+
.getValue(context, String::class.java)
361+
----
362+
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
363+
======

spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,25 @@ void nullSafeSelectFirst() {
689689
assertThat(inventor).isNull();
690690
}
691691

692+
@Test
693+
void nullSafeSelectFirstAndPropertyAccess() {
694+
IEEE society = new IEEE();
695+
StandardEvaluationContext context = new StandardEvaluationContext(society);
696+
String expression = "members?.^[nationality == 'Serbian']?.name"; // <1>
697+
698+
// evaluates to "Nikola Tesla"
699+
String name = parser.parseExpression(expression)
700+
.getValue(context, String.class);
701+
assertThat(name).isEqualTo("Nikola Tesla");
702+
703+
society.members = null;
704+
705+
// evaluates to null - does not throw a NullPointerException
706+
name = parser.parseExpression(expression)
707+
.getValue(context, String.class);
708+
assertThat(name).isNull();
709+
}
710+
692711
@Test
693712
@SuppressWarnings("unchecked")
694713
void nullSafeSelectLast() {

0 commit comments

Comments
 (0)