Skip to content

Commit

Permalink
Document null-safe index operator in SpEL
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrannen committed Mar 23, 2024
1 parent 38c473f commit 218a148
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[[expressions-operator-safe-navigation]]
= Safe Navigation Operator

The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
The safe navigation operator (`?.`) is used to avoid a `NullPointerException` and comes
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
Expand Down Expand Up @@ -81,6 +81,64 @@ For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
`max(int, int)` method will be invoked on the `#calculator`.
====

[[expressions-operator-safe-navigation-indexing]]
== Safe Index Access

Since Spring Framework 6.2, the Spring Expression Language supports safe navigation for
indexing into the following types of structures.

* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-arrays-and-collections[arrays and collections]
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-strings[strings]
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-maps[maps]
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-objects[objects]

The following example shows how to use the safe navigation operator for indexing into
a list (`?.[]`).

[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
EvaluationContext context = new StandardEvaluationContext(society);
// evaluates to Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression("members?.[0]") // <1>
.getValue(context, Inventor.class);
society.members = null;
// evaluates to null - does not throw an exception
inventor = parser.parseExpression("members?.[0]") // <2>
.getValue(context, Inventor.class);
----
<1> Use null-safe index operator on a non-null `members` list
<2> Use null-safe index operator on a null `members` list
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
// evaluates to Inventor("Nikola Tesla")
var inventor = parser.parseExpression("members?.[0]") // <1>
.getValue(context, Inventor::class.java)
society.members = null
// evaluates to null - does not throw an exception
inventor = parser.parseExpression("members?.[0]") // <2>
.getValue(context, Inventor::class.java)
----
<1> Use null-safe index operator on a non-null `members` list
<2> Use null-safe index operator on a null `members` list
======

[[expressions-operator-safe-navigation-selection-and-projection]]
== Safe Collection Selection and Projection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ into various structures.
NOTE: Numerical index values are zero-based, such as when accessing the n^th^ element of
an array in Java.

TIP: See the xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
section for details on how to navigate object graphs and index into various structures
using the null-safe operator.

[[expressions-property-navigation]]
== Property Navigation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@
* <li>Objects: the property with the specified name</li>
* </ul>
*
* <h3>Null-safe Indexing</h3>
*
* <p>As of Spring Framework 6.2, null-safe indexing is supported via the {@code '?.'}
* operator. For example, {@code 'colors?.[0]'} will evaluate to {@code null} if
* {@code colors} is {@code null} and will otherwise evaluate to the 0<sup>th</sup>
* color.
*
* @author Andy Clement
* @author Phillip Webb
* @author Stephane Nicoll
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,24 @@ void nullSafePropertyAccess() {
assertThat(city).isNull();
}

@Test
void nullSafeIndexing() {
IEEE society = new IEEE();
EvaluationContext context = new StandardEvaluationContext(society);

// evaluates to Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression("members?.[0]") // <1>
.getValue(context, Inventor.class);
assertThat(inventor).extracting(Inventor::getName).isEqualTo("Nikola Tesla");

society.members = null;

// evaluates to null - does not throw an Exception
inventor = parser.parseExpression("members?.[0]") // <2>
.getValue(context, Inventor.class);
assertThat(inventor).isNull();
}

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

0 comments on commit 218a148

Please sign in to comment.