Null‑Conditional Operator
The null-conditional operator (?) provides a safe way to access properties and methods on objects (?) and indexing into collections (?[]) where the receiver has the potential to be null.
It prevents the runtime exception 1090 (Attempted access via null object reference) from occurring by automatically short‑circuiting the remainder of the expression when the receiver evaluates to null. When short‑circuiting occurs, the result of the expression is the null representation of whatever type the expression chain is declared as, which could be a null object reference or a null primitive value.
The null-conditional operator allows you to write the following line of code instead of the following lines of code that follow this example.
name := person?.name;
if person <> null then
name := person.name;
else
name := null;
endif;
If the person reference is null, the expression evaluates to null and the right‑hand side (that is, .name) is not evaluated, avoiding a 1090 exception. The person?.name expression therefore evaluates to be a null String primitive type value.
Similarly, it can be used to write the collection access logic in the first line of code, instead of the lines of code that follow this example.
person := employees?[“Wilbur”];
if employees <> null then
person := employees[“Wilbur”];
else
person := null;
endif;
If the collection reference employees is null, the expression evaluates to null and the index lookup is not attempted, avoiding a 1090 exception. The employees?["Wilbur"] expression therefore evaluates to be a null object reference.
Syntax
The null-conditional operator syntax is as follows.
-
The property and method access null‑conditional operator syntax is as follows.
expression?.member
The expression identifier evaluates to an object reference, which cannot be a primitive type.
The member identifier is a property or method belonging to the receiver object type.
-
The collection subscript indexing null‑conditional operator syntax is as follows.
expression?[index-expression]
The expression identifier evaluates to an Array or Dictionary reference.
The index-expression identifier is a valid index expression for the specified collection reference.
As the null-conditional operator applies only to its direct receiver, if expression results in a non‑null object reference, any exceptions that would occur from the equivalent non‑null‑conditional expression will also occur. For example, array?[0] would result in exception 1300 (Array index out of bounds) being raised if the array reference is non-null.
In addition, a?.b.c would result in exception 1090 (Attempted access via null object reference) being raised when attempting to access .c if the object reference a is non-null but its property b is null.
Evaluation Semantics
For the address := person?.myAddress.fullAddress(); line of code, evaluation proceeds as follows.
-
Evaluate person.
-
If person is null:
-
.myAddress.fullAddress() is not evaluated.
-
Null is returned from the expression and assigned to address.
-
-
If person is non-null:
-
person.myAddress.fullAddress() is evaluated normally.
-
The value of the fullAddress method is assigned to address.
-
The ?. operator evaluates its left‑hand operand no more than once. This means that if person evaluated to non‑null, it is not re‑evaluated during step 3.a when the full expression is evaluated.
This guarantees that it cannot be changed to null after being verified as non‑null, and any side‑effects will not be triggered twice; for example, if person is a property with a mapping method, the mapping method would be called once only. Similarly, if person was instead a method call, it would call the method once only.
Chaining
Chaining is the most powerful and common use of null-conditional operators.
An expression can include multiple null‑conditional operators and it can be a combination of method calls, property accesses, and collection indexing. For example:
zip := orders?[orderNumber]?.customer?.getAddress()?.zipCode;
This example would have similar behavior to null‑checking every part of the following expression.
if orders <> null and orders[orderNumber] <> null and orders[orderNumber].customer <> null and orders[orderNumber].customer.getAddress() <> null then
zip := orders[orderNumber].customer.getAddress().zipCode;
else
zip := null;
endif;
With the added benefit that the full expression is evaluated once only, it means that the orders collection is indexed once only, the customer property of the Orders class is read once only, and the getAddress method is called once only, which is not true of the long‑form example in the above code.
Short-circuiting scope
The null‑conditional operator short‑circuits only evaluation of the remainder of the chain of dot (.) notation and index (/) notation operands to its right. If the expression is part of a larger expression that includes unary operators (+, -, not), arithmetic operators (^, +, -, *, /, mod, div), boolean operators (and, or), relational operators (=, <, >, <=, >=), or it is scoped by parentheses (), when evaluated, if the null‑conditional receiver evaluates to null, the remainder of that chain is short‑circuited and the null‑conditional expression yields null. Evaluation of the enclosing expression continues as normal.
Without this clarification, it can incorrectly be assumed that the following line of code behaves like the code in the lines of code that follow.
result := a?.b.c + d;
if a = null then
result := null;
else
result := a.b.c + d;
endif;
In reality, however, it behaves as follows.
temp := null;
if a <> null then
temp := a.b.c;
endif;
result := temp + d;
Similarly, you can use parentheses to limit the scope of the null‑conditional operator short‑circuiting. This can be useful in scenarios involving primitives type values, as follows.
priceString := (item?.price).formatPrice();
temp := null;
if item <> null then
temp := item.price;
endif;
priceString := temp.formatPrice();
In this scenario, if the value of item is null, item?.price short-circuits to null (in this case, the null Decimal representation of 0.0), but we still call the Decimal primitive type method formatPrice(), which can handle null Decimal values and assign a valid String value back to the priceString variable.
Without the parentheses, if the value of item was null, the full item?.price.formatPrice() expression would have been short‑circuited and resulted in a null String value being assigned to the priceString variable.
Limitations
Limitations have been implemented to avoid ambiguous behavior and reduce potential confusing scenarios related to null-conditional operators, these are as follows:
-
The declared type of the null-conditional operator receiver must always be an object reference. Primitive types are not supported. This is due to primitive methods being called regardless of the primitive having a null/non-null value. To avoid any confusion, they are disallowed.
-
The null-conditional operator cannot be used to:
-
Access type methods. Similarly, this is because type methods are called regardless of the receiver being null / non-null.
-
Perform type guards, because type guards evaluate regardless of the receiver being a null/non-null value. However, the receiver of a null‑conditional operator can be a type guard.
-
-
The null-conditional operator cannot be used to directly or indirectly access a type constant; that is, when accessing a type constant via a chained expression, there cannot be any null-conditional operators before the constant access. This is because these expressions are not evaluated at run time, the constant value is directly stored in the code tree. It would be confusing for the null-conditional operator to not short-circuit when expected.
-
The receiver of a null‑conditional operator cannot be a meta syntax expression. Meta syntax expressions always have a value, so a null‑conditional check is redundant.
-
The null-conditional operator cannot be used in assignable expressions; that is, it cannot be used:
-
On the left‑hand side expression of an assignment statement
-
In an expression that is an output or io parameter
-
As a control variable in a foreach instruction
-
2025 R2 and higher
