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.

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.

  1. Evaluate person.

  2. If person is null:

    1. .myAddress.fullAddress() is not evaluated.

    2. Null is returned from the expression and assigned to address.

  3. If person is non-null:

    1. person.myAddress.fullAddress() is evaluated normally.

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

2025 R2 and higher