Safe Navigation Operator

The safe navigation operator (?.) is used to avoid a NullPointerException and comes from the 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 for the particular null-safe operation instead of throwing an exception.

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.

Safe Property and Method Access

The following example shows how to use the safe navigation operator for property access (?.).

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

// evaluates to "Smiljan"
String city = parser.parseExpression("placeOfBirth?.city") (1)
		.getValue(context, tesla, String.class);

tesla.setPlaceOfBirth(null);

// evaluates to null - does not throw NullPointerException
city = parser.parseExpression("placeOfBirth?.city") (2)
		.getValue(context, tesla, String.class);
1 Use safe navigation operator on non-null placeOfBirth property
2 Use safe navigation operator on null placeOfBirth property
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))

// evaluates to "Smiljan"
var city = parser.parseExpression("placeOfBirth?.city") (1)
		.getValue(context, tesla, String::class.java)

tesla.setPlaceOfBirth(null)

// evaluates to null - does not throw NullPointerException
city = parser.parseExpression("placeOfBirth?.city") (2)
		.getValue(context, tesla, String::class.java)
1 Use safe navigation operator on non-null placeOfBirth property
2 Use safe navigation operator on null placeOfBirth property

The safe navigation operator also applies to method invocations on an object.

For example, the expression #calculator?.max(4, 2) evaluates to null if the #calculator variable has not been configured in the context. Otherwise, the max(int, int) method will be invoked on the #calculator.

Safe Index Access

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

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

  • Java

  • Kotlin

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

Safe Collection Selection and Projection

The Spring Expression Language supports safe navigation for collection selection and collection projection via the following operators.

  • null-safe selection: ?.?

  • null-safe select first: ?.^

  • null-safe select last: ?.$

  • null-safe projection: ?.!

The following example shows how to use the safe navigation operator for collection selection (?.?).

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.?[nationality == 'Serbian']"; (1)

// evaluates to [Inventor("Nikola Tesla")]
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
		.getValue(context);

society.members = null;

// evaluates to null - does not throw a NullPointerException
list = (List<Inventor>) parser.parseExpression(expression)
		.getValue(context);
1 Use null-safe selection operator on potentially null members list
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression = "members?.?[nationality == 'Serbian']" (1)

// evaluates to [Inventor("Nikola Tesla")]
var list = parser.parseExpression(expression)
		.getValue(context) as List<Inventor>

society.members = null

// evaluates to null - does not throw a NullPointerException
list = parser.parseExpression(expression)
		.getValue(context) as List<Inventor>
1 Use null-safe selection operator on potentially null members list

The following example shows how to use the "null-safe select first" operator for collections (?.^).

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
	"members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; (1)

// evaluates to Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

society.members = null;

// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);
1 Use "null-safe select first" operator on potentially null members list
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression =
	"members?.^[nationality == 'Serbian' || nationality == 'Idvor']" (1)

// evaluates to Inventor("Nikola Tesla")
var inventor = parser.parseExpression(expression)
		.getValue(context, Inventor::class.java)

society.members = null

// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
		.getValue(context, Inventor::class.java)
1 Use "null-safe select first" operator on potentially null members list

The following example shows how to use the "null-safe select last" operator for collections (?.$).

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
	"members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; (1)

// evaluates to Inventor("Pupin")
Inventor inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

society.members = null;

// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);
1 Use "null-safe select last" operator on potentially null members list
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression =
	"members?.$[nationality == 'Serbian' || nationality == 'Idvor']" (1)

// evaluates to Inventor("Pupin")
var inventor = parser.parseExpression(expression)
		.getValue(context, Inventor::class.java)

society.members = null

// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
		.getValue(context, Inventor::class.java)
1 Use "null-safe select last" operator on potentially null members list

The following example shows how to use the safe navigation operator for collection projection (?.!).

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);

// evaluates to ["Smiljan", "Idvor"]
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (1)
		.getValue(context, List.class);

society.members = null;

// evaluates to null - does not throw a NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (2)
		.getValue(context, List.class);
1 Use null-safe projection operator on non-null members list
2 Use null-safe projection operator on null members list
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)

// evaluates to ["Smiljan", "Idvor"]
var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (1)
		.getValue(context, List::class.java)

society.members = null

// evaluates to null - does not throw a NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (2)
		.getValue(context, List::class.java)
1 Use null-safe projection operator on non-null members list
2 Use null-safe projection operator on null members list

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.

  • Java

  • Kotlin

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