Introduction

The error list object has no attribute length is one of the most common Jinja2 mistakes in Ansible. It occurs when you try to access length as a Python attribute (.length) instead of using Jinja2's length filter (| length). This guide explains why, shows the fix, and covers all the essential Jinja2 list operations you should know.

The Error

# WRONG — causes "list object has no attribute length"
- name: Display list length
  ansible.builtin.debug:
    msg: "{{ my_list.length }}"

Error output:

fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. 
The error was: 'list object' has no attribute 'length'"}

The Fix

Use the | length Jinja2 filter:

# CORRECT
- name: Display list length
  ansible.builtin.debug:
    msg: "{{ my_list | length }}"

Why This Happens

Ansible uses Jinja2 for templating, not raw Python. In Jinja2:

  • Attributes (dot notation) access object properties: my_dict.key
  • Filters (pipe notation) transform values: my_list | length

Python lists have a len() function, not a .length attribute. Jinja2 provides the length filter as the equivalent:

PythonJinja2 (Ansible)
len(my_list){{ my_list | length }}
my_list.sort(){{ my_list | sort }}
", ".join(my_list){{ my_list | join(", ") }}

Practical Examples

Count Items in a List

vars:
  packages:
    - nginx
    - postgresql
    - redis
    - memcached

tasks:
  - name: Show package count
    ansible.builtin.debug:
      msg: "Installing {{ packages | length }} packages"
    # Output: "Installing 4 packages"

Conditional Based on List Length

- name: Warn if too many hosts
  ansible.builtin.debug:
    msg: "WARNING: {{ ansible_play_hosts | length }} hosts in play — consider using serial"
  when: ansible_play_hosts | length > 20

Check If List Is Empty

- name: Skip if no errors found
  ansible.builtin.debug:
    msg: "Processing {{ errors | length }} errors"
  when: errors | length > 0

# Alternative: use truthiness
- name: Skip if no errors (simpler)
  ansible.builtin.debug:
    msg: "Processing errors"
  when: errors  # Empty list is falsy

Use Length in Loops

- name: Show progress
  ansible.builtin.debug:
    msg: "Processing {{ ansible_loop.index }} of {{ my_list | length }}: {{ item }}"
  loop: "{{ my_list }}"
  loop_control:
    extended: true

Essential Jinja2 List Filters

Beyond length, here are the most useful list filters in Ansible:

Sorting

# Sort ascending
msg: "{{ my_list | sort }}"

# Sort descending
msg: "{{ my_list | sort(reverse=true) }}"

# Sort by attribute
msg: "{{ users | sort(attribute='name') }}"

Joining

# Join with comma
msg: "{{ packages | join(', ') }}"
# Output: "nginx, postgresql, redis"

# Join for command line
msg: "apt install {{ packages | join(' ') }}"

Filtering

# Select items matching criteria
msg: "{{ numbers | select('greaterthan', 5) | list }}"

# Reject items
msg: "{{ packages | reject('equalto', 'redis') | list }}"

# Select by attribute
msg: "{{ users | selectattr('active', 'equalto', true) | list }}"

Extracting

# Get unique values
msg: "{{ tags | unique }}"

# Get first/last
msg: "{{ my_list | first }}"
msg: "{{ my_list | last }}"

# Flatten nested lists
msg: "{{ nested_list | flatten }}"

# Extract attribute from list of dicts
msg: "{{ users | map(attribute='name') | list }}"

Set Operations

# Union (combine unique)
msg: "{{ list1 | union(list2) }}"

# Intersection (common items)
msg: "{{ list1 | intersect(list2) }}"

# Difference
msg: "{{ list1 | difference(list2) }}"

# Symmetric difference
msg: "{{ list1 | symmetric_difference(list2) }}"

Math on Lists

# Sum
msg: "{{ numbers | sum }}"

# Min/Max
msg: "{{ numbers | min }}"
msg: "{{ numbers | max }}"

Similar Attribute Errors

The same pattern applies to other common mistakes:

# WRONG                          # CORRECT
{{ my_list.sort() }}             {{ my_list | sort }}
{{ my_list.reverse() }}          {{ my_list | reverse | list }}
{{ my_string.upper() }}          {{ my_string | upper }}
{{ my_string.lower() }}          {{ my_string | lower }}
{{ my_string.strip() }}          {{ my_string | trim }}
{{ my_dict.keys() }}             {{ my_dict | dict2items | map(attribute='key') | list }}
{{ my_list.append('x') }}        # Not possible — use: {{ my_list + ['x'] }}

Real-World Patterns

Dynamic Inventory Sizing

- name: Calculate worker count
  ansible.builtin.set_fact:
    worker_count: "{{ [ansible_processor_vcpus, groups['workers'] | length] | min }}"

Validate Input Lists

- name: Ensure at least 3 DNS servers configured
  ansible.builtin.assert:
    that:
      - dns_servers | length >= 3
    fail_msg: "Need at least 3 DNS servers, got {{ dns_servers | length }}"

Batch Processing

- name: Process in batches of 10
  ansible.builtin.debug:
    msg: "Batch {{ ansible_loop.index }}: {{ item }}"
  loop: "{{ my_list | batch(10) | list }}"
  loop_control:
    extended: true

Conclusion

The list object has no attribute length error has a one-line fix: replace .length with | length. The underlying principle is simple — Jinja2 uses filters (pipe syntax) for operations that Python handles with methods (dot syntax). Once you internalize this pattern, you'll avoid an entire class of Ansible templating errors: use | sort not .sort(), | upper not .upper(), | join not .join().