Operators are widely used in Azure Bicep. Commonly used operators include arithmetic operators like + and - for performing calculations, as well as comparison operators such as == and != and there are many more. Additionally, there are less familiar operators like !, ??, and .?, each offering unique functionality.
In this blog you will learn about the following three nullability operators:
- Null-forgiving
! - Coalesce
?? - Safe-dereference
.?
Null-forgiving operator
The null-forgiving operator, also known as the null-suppression operator, is used to suppress null warnings for the preceding expression. The operator used for null-forgiving is !. Suppression is done during compile time. If you’re familiar with C#, the null-forgiving operator might be recognisable because it works similarly.
How to use null-forgiving
In the Bicep snippet below, you see an array of groceries. This array is used in the output and the filter function is used to get a specific product using the price property with the price is greater than 1 expression. The return of the filter function is an array, but the Bicep output expects an object, so the first function is used to retrieve the first item from the array.
| var varGroceries = [ | |
| { | |
| productName: 'Bread' | |
| price: 2 | |
| } | |
| { | |
| productName: 'Milk' | |
| price: 1 | |
| } | |
| ] | |
| output outPriceHigherThan1 object = first(filter(varGroceries, product => product.price > 1)) |
The problem here is that Bicep will throw a warning because the output of the first() function is not guaranteed and can be of type null, while the Bicep output expects a value of type object.
You will see a warning like the one below:

In some cases, like the one above, for example, you know that the output of the first() function is guaranteed, so this is a perfect use-case to suppress the warning using the ! operator:

If a null pointer occurs during runtime, it will be handled by a deployment failure.
Coalesce operator
The coalesce operator is a logical operator that returns the first non-null value. The operator used for coalesce is ??. The associativity of coalesce is left-to-right, which means that the null check always starts on the left side first.
The ?? operator evaluates if the left-side value of the ?? operator is null or not. If the left-side has a value, then that value is returned, but if the value is null, then the right-side of the ?? is evaluated. When the left-side value is non-null, the right-side is skipped for evaluation because the left-side value has already been returned.
In Bicep, the coalesce operator can handle more than just two values, as in a ?? b ?? c ?? d. Based on its left-to-right associativity, the evaluation of this expression is grouped as follows: (a ?? b) ?? c ?? d.
How to use coalesce
Coalesce is a useful operator for checking if specific parameters or variables are null. In the Bicep snippet below, you see a scenario where a fallback value is set when the user input is null:
| param parName string? | |
| param parLocation string = resourceGroup().location | |
| resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = { | |
| name: parName ?? 'fallback-kv-name' | |
| location: parLocation | |
| properties: { | |
| enabledForDeployment: true | |
| enabledForTemplateDeployment: true | |
| enabledForDiskEncryption: true | |
| tenantId: tenant().tenantId | |
| sku: { | |
| name: 'standard' | |
| family: 'A' | |
| } | |
| } | |
| } |
This can be useful when you are building Bicep building blocks, for example. This way, the user has control over the name of the key vault. However, if the value of parName is null (because of the nullable type string?), then a fallback value is used.
When both left and right values are null and there is no fallback value, it will throw a compile-time warning, as seen in the image below:

Safe-dereference operator
The safe-dereference operator is used to access the properties of an object or array in a safe way. The operator used for safe-dereference is .?. This operator helps prevent runtime errors that can occur when trying to access properties in objects or arrays that do not exist.
When you are not using the safe-dereference operator and you try to access a property that does not exist, it will break your deployment due to the error “property ‘x’ does not exist”.
How to use safe-dereference
In the Bicep snippet below you see the definition of the following resources and type:
- A user-defined (object) type called
cnameTypeis created and holds required and optional properties. The optional properties are nullable and are not required to be defined in the object. - The resources
resDnsZoneand the child resourceresCnameRecorddefinitions, are created and use the parameterparCnameRecord, which has thecnameTypeas its type. - A parameter called
parCnameRecordwith typecnameTypeis created and is used in the resource definitionresCnameRecord.
| param parCnameRecord cnameType = { | |
| name: 'cname_bicepazure' | |
| ttl: 3600 | |
| cnameRecord: { | |
| cname: 'copilot' | |
| } | |
| } | |
| resource resDnsZone 'Microsoft.Network/dnsZones@2023-07-01-preview' = { | |
| name: 'bicep.azure' | |
| location: 'global' | |
| resource resCnameRecord 'cname' = { | |
| name: parCnameRecord.name | |
| properties: { | |
| metadata: parCnameRecord.metadata | |
| TTL: parCnameRecord.ttl | |
| CNAMERecord: parCnameRecord.cnameRecord | |
| } | |
| } | |
| } | |
| type cnameType = { | |
| name: string | |
| metadata: object? | |
| ttl: int | |
| cnameRecord: { | |
| cname: string | |
| }? | |
| } |
The above Bicep compiles successfully, however, it will throw a runtime error when being deployed.
In the snippet above, the parameter is missing the metadata property, and because this property is null, it will throw the following error during deployment:
The language expression property 'metadata' doesn't exist, available properties are 'name, ttl, cnameRecord’.
The solution here is to apply the safe-dereference operator. This operator will check if the property exists; if not, the value is null and will not be used during deployment:
| resource resDnsZone 'Microsoft.Network/dnsZones@2023-07-01-preview' = { | |
| name: 'bicep.azure' | |
| location: 'global' | |
| resource resCnameRecord 'cname' = { | |
| name: parCnameRecord.name | |
| properties: { | |
| metadata: parCnameRecord.?metadata | |
| TTL: parCnameRecord.ttl | |
| CNAMERecord: parCnameRecord.?cnameRecord | |
| } | |
| } | |
| } |
Before the safe-dereference operator the other option was to use the contains() function with ternary condition like this:
metadata: contains(parCnameRecord, 'metadata') ? parCnameRecord.metadata : null
Conclusion
In conclusion, the null-forgiving, coalesce, and safe-dereference operators in Azure Bicep provide solutions for handling null values, performing null checks, and safely accessing properties of objects or arrays. Understanding these operators can improve your author experience with Azure Bicep. This is how I use the above operators. Besides these examples, there are many more use cases for which you can use these operators.
