Terraform rejects null values in lists because they create ambiguity in configuration. This error typically occurs with conditional expressions or data source outputs. Use Terraform's built-in functions like compact() or filtering expressions to remove nulls before passing them to list arguments.
In Terraform HCL, the null value represents "absence or omission." While null is valid for individual arguments (where Terraform omits the argument and uses defaults), lists and other collection types cannot contain null elements. When Terraform encounters a null value inside a list—either from a conditional expression, data source output, or variable—it fails validation with this error. This strict validation prevents ambiguous configurations that could lead to unexpected infrastructure state. The error typically manifests during terraform plan when Terraform evaluates your configuration and discovers null values nested within list structures.
The compact() function removes null and empty string values from a list of strings. This is the simplest fix for most cases.
# Instead of this (may contain nulls):
variable "tags" {
default = [var.optional_tag, "production"]
}
# Use this:
locals {
tags = compact([var.optional_tag, "production"])
}
resource "aws_instance" "example" {
tags = local.tags
}The compact() function only works with string lists. For other types, use the filtering approach below.
For non-string lists or more control, use a for expression with a conditional filter.
# For any list type, filter out nulls:
locals {
security_group_ids = [for sg_id in var.optional_security_groups : sg_id if sg_id != null]
}
resource "aws_instance" "example" {
vpc_security_group_ids = local.security_group_ids
}
# Or filter directly in the resource argument:
resource "aws_instance" "example" {
vpc_security_group_ids = [for sg_id in var.sg_list : sg_id if sg_id != null]
}This approach works with any data type and gives you complete control over filtering logic.
Instead of allowing null values, use coalesce() to substitute a default value when a variable is null.
# Instead of:
variable "optional_protocol" {
default = null
}
resource "aws_security_group_rule" "example" {
from_port = 443
to_port = 443
protocol = var.optional_protocol # This could be null!
}
# Use coalesce to provide a default:
resource "aws_security_group_rule" "example" {
from_port = 443
to_port = 443
protocol = coalesce(var.optional_protocol, "tcp")
}This ensures a valid value is always present, eliminating null entirely.
When a data source lookup might return null, use try() to catch the error and provide an empty list or fallback.
# Instead of:
data "aws_ami" "ubuntu" {
filter {
name = "name"
values = [var.optional_ami_name] # Might be null!
}
}
# Use try() with a fallback:
locals {
# Returns [] if the lookup fails or returns null
ami_list = try([data.aws_ami.ubuntu.id], [])
}
resource "aws_instance" "example" {
ami = try(data.aws_ami.ubuntu.id, var.fallback_ami_id)
}The try() function catches errors and exceptions, allowing you to provide safe fallback values.
If a variable is genuinely optional, make it clear in your variable definition and handle it explicitly.
variable "additional_security_groups" {
type = list(string)
default = [] # Use empty list as default, not null
description = "Additional security groups. Leave empty to skip."
}
variable "optional_tag" {
type = string
default = "" # Use empty string instead of null
description = "Optional tag value. Leave empty to omit."
}
# Then filter out empty strings if needed:
locals {
tags = [for tag in [var.optional_tag, "required"] : tag if tag != ""]
}This makes it explicit that these values are optional and avoids null entirely.
Advanced handling: Terraform 1.5+ improved the compact() function to handle non-string list types. For older versions, the for expression filtering approach is more reliable. When dealing with optional module inputs, consider using dynamic blocks instead of lists—they handle null gracefully:
resource "aws_instance" "example" {
ami = var.ami_id
dynamic "root_block_device" {
for_each = var.root_block_config != null ? [var.root_block_config] : []
content {
volume_size = root_block_device.value.size
}
}
}This pattern avoids lists altogether for optional configurations.
Error: Error rendering template: template not found
How to fix "template not found" error in Terraform
Error: Error generating private key
How to fix 'Error generating private key' in Terraform
Error creating Kubernetes Service: field is immutable
How to fix "field is immutable" errors in Terraform
Error: Error creating local file: open: permission denied
How to fix "Error creating local file: permission denied" in Terraform
Error: line endings have changed from CRLF to LF
Line endings have changed from CRLF to LF in Terraform