tSM SpEL List Operations
Complete documentation for list behavior, extensions, SpEL operators, and ArrayList methods.
Introduction
Lists in tSM SpEL are a core data structure used across:
- service connector responses
- spreadsheet rows
- aggregation pipelines
- transformation logic
- workflow scripts
- condition evaluation
- UI expression logic
In tSM SpEL, a list literal:
#persons = [
{'name': 'Alice'},
{'name': 'Bob'}
]
is represented internally as java.util.ArrayList. This means:
- Core SpEL operators apply (selection, projection, …)
- tSM List Extension Methods apply (sum, groupBy, forEach, …)
- Native Java ArrayList methods apply (add, clear, remove, …)
This guide covers all three layers, explains how they work, why they exist, and when to use which.
Variable Initialization in tSM SpEL
In most cases, the tSM SpEL evaluator automatically infers the correct data type of a variable based on the assigned value. For example:
#name = 'Alice' // inferred as String
#age = 42 // inferred as Integer
#items = [1,2,3] // inferred as java.util.ArrayList
However, some operations require the variable to be initialized explicitly before use. This is especially important when you want to call a method on a list variable that has not been initialized yet.
Why initialization matters
If a variable is undefined (not assigned yet), SpEL treats it as null.
Calling any list method (including native ArrayList methods such as .add(...)) on null results in an error:
Example — calling .add() on an uninitialized list
#items.add({'name':'Alice'})
Result
Error: Cannot invoke method 'add' on null object
This happens because #items has no value yet, so SpEL cannot determine what .add() should operate on.
Correct way: initialize the empty list first
To explicitly create an empty list, assign:
#items = []
This produces a new java.util.ArrayList.
Now list operations work as expected:
Example — properly initialized list
#items = []
#items.add({'name':'Alice'})
#items.add({'name':'Bob'})
#items
Result
[
{'name':'Alice'},
{'name':'Bob'}
]
When explicit initialization is required
You should explicitly initialize a list when:
✔ You want to build a list dynamically using .add() or .addAll()
#logs = []
#logs.add('START')
#logs.add('FINISH')
✔ You want to gradually construct a collection inside a forEach
#result = []
#numbers = [1,2,3]
#numbers.forEach(#n, #result.add(#n * 10))
#result
✔ You want to ensure a variable is a list, not accidentally null
Some connectors return null when no records are present; converting it to a list may be safer:
#rows = #rows == null ? [] : #rows
✔ You want to avoid type ambiguity
If you later do:
#items = []
#items.add(123)
#items.add({'x':1})
SpEL won't complain — but your workflow might. Initializing avoids “null-type guessing” problems.
When explicit initialization is NOT required
You do not need to initialize a list if:
-
you assign a list literal:
#items = [ {'name':'Alice'} ] -
the list comes from:
- a connector response
- a spreadsheet
- a service call
- a previous expression
These are always valid lists.
Core SpEL List Operators
Core SpEL operators are processed by the Spring Expression Engine, not by tSM. They work on any list, including lists returned from connectors and spreadsheets.
These two operators are foundational:
- Selection:
list.?[ condition ] - Projection:
list.![ expression ]
They behave functionally and never modify the list.
Selection Operator — .?[ condition ]
What it does
Filters the list by evaluating a condition for each element. Every element where the condition evaluates to true is included in the resulting list.
How it works internally
-
The engine iterates over the list.
-
For each element:
- Sets
#thisto the current element. - Evaluates the condition.
- Sets
-
Builds a new list with elements for which the condition was true.
Correct syntax
list.?[ <boolean expression using #this> ]
When to use it
- Filtering rows returned from connectors.
- Filtering children in hierarchical data.
- Powerful alternative to complex
filter(...)calls.
Example – Select active persons
#persons = [
{'name': 'Alice', 'active': true },
{'name': 'Bob', 'active': false},
{'name': 'Carol', 'active': true }
]
#persons.?[#this.active]
Result explanation:
#this.activeistruefor Alice and Carol.- A new list with only those two persons is returned.
Example – Combined conditions
#persons = [
{'name': 'Alice', 'age': 30, 'role': 'ADMIN'},
{'name': 'Bob', 'age': 40, 'role': 'USER'},
{'name': 'Carol', 'age': 35, 'role': 'ADMIN'}
]
#persons.?[
#this.role == 'ADMIN' and #this.age >= 35
]
Why this is correct
The selection operator allows any boolean expression, including:
- comparisons
- logical operators
- nested access (
#this.role,#this.address.city) - function calls (e.g.
isAfter) - safety navigation (
?.)
Example – Selecting based on nested lists
#companies = [
{
'name': 'A',
'departments': [
{'code': 'IT'}, {'code': 'HR'}
]
},
{
'name': 'B',
'departments': [
{'code': 'IT'}, {'code': 'SALES'}
]
}
]
#companies.?[
#this.departments.?[#this.code == 'IT'].size() > 0
]
Explanation
We filter companies by checking if their departments list contains at least one with code == 'IT'.
Projection Operator — .![ expression ]
What it does
Maps every element of a list into something else:
- a single field
- a computed value
- a complex object/map
Syntax
list.![ <expression using #this> ]
When to use
- Extracting fields (IDs, names, totals…)
- Building new objects from list elements
- Transformations before passing values into connectors
- Preparing list-of-values for dropdowns
Example – Extract all names
#persons = [
{'name': 'Alice', 'age': 30},
{'name': 'Bob', 'age': 40},
{'name': 'Carol', 'age': 35}
]
#persons.![#this.name]
Explanation
Projection produces:
['Alice', 'Bob', 'Carol']
Example – Complex projection
#persons = [
{'name': 'Alice', 'role': 'ADMIN'},
{'name': 'Bob', 'role': 'USER'}
]
#persons.![{
'label': #this.name + ' (' + #this.role + ')',
'role': #this.role
}]
Explanation
Transforms each input map into a new map.
Example – Selection + projection chain
This is extremely common in real workflows.
#persons = [
{'name': 'Alice', 'active': true},
{'name': 'Bob', 'active': false},
{'name': 'Carol', 'active': true}
]
#persons.?[#this.active].![#this.name]
Explanation
- Select only active persons
- Extract their names
tSM SpEL List Extension Methods
These are custom, domain-tailored functions provided by SpelContextContributorListSupport.
They provide:
- aggregations (
sum,average) - grouping (
groupBy,toMap) - transformations (
flatMap,mapNotNull) - searching (
find,findIndex) - logic (
any,all,none) - chunking (
chunk,sliding) - iteration (
forEach)
These do not mutate the original list unless specified.
Aggregations
sum() – simple numeric sum
Explanation
- Converts values to
Long. - Only for lists already containing numeric or numeric-string values.
- Throws an error for non-numeric strings.
Example
#amounts = ['100','200','300']
#amounts.sum()
sum(mapperExpr) / sum(#var, mapperExpr)
Explanation
Mapped version:
- Evaluates expression per element
- Extracts numeric values
- Returns
Double #indexis available- Useful for summing object fields
Example
#orders = [
{'id':1,'total':100},
{'id':2,'total':200},
{'id':3,'total':300}
]
#orders.sum(#this.total)
average(mapperExpr)
Explanation
- Uses same mapping rules as
sum - Empty list → returns
0.0
Example
#orders = [
{'total':100},
{'total':50},
{'total':150}
]
#orders.average(#this.total)
Grouping and Mapping
groupBy(mapperExpr)
Explanation
- Groups list elements into a map, effectively
key -> List(elements with that key) - Key can be: field, expression, computed key, composite key
Example
#persons = [
{'name':'Alice','role':'ADMIN'},
{'name':'Bob', 'role':'USER'},
{'name':'Carol','role':'ADMIN'}
]
#persons.groupBy(#this.role)
toMap(keyExpr, valueExpr)
Explanation
- Builds a map from a list
- Key collisions overwrite the previous value
- Very useful for lookup dictionaries
Example
#persons = [
{'id':1,'name':'Alice'},
{'id':2,'name':'Bob'}
]
#persons.toMap(#this.id, #this.name)
flatMap(mapperExpr)
Explanation
- If mapper returns a list → its contents are appended
- If returns a value → appended directly
- If returns
null→ ignored - Flattens one level of nesting
Example
#parents = [
{'name':'P1', 'children':['C1','C2']},
{'name':'P2', 'children':['C3']}
]
#parents.flatMap(#this.children)
mapNotNull(mapperExpr)
Explanation
- Maps elements
- Drops all
nullresults - Useful for safely extracting optional values
Example
#rows = [
{'value':'A'},
{'value':null},
{'value':'B'}
]
#rows.mapNotNull(#this.value)
Logic & Searching
any(condition)
True if at least one element matches
#orders = [
{'total':50},
{'total':150}
]
#orders.any(#this.total > 100)
all(condition)
True if all match
#persons = [{'valid':true},{'valid':true}]
#persons.all(#this.valid)
none(condition)
True if none match
#persons = [{'disabled':false},{'disabled':false}]
#persons.none(#this.disabled)
find(condition)
Returns the first matching element
#persons = [
{'name':'Alice','age':30},
{'name':'Bob', 'age':40}
]
#persons.find(#this.age > 35)
findIndex(condition)
Returns index of first match
#persons = [
{'age':30},
{'age':40}
]
#persons.findIndex(#this.age > 35)
Chunking & Sliding
chunk(size)
Splits into sublists of fixed size.
#persons = [{'name':'A'},{'name':'B'},{'name':'C'},{'name':'D'}]
#persons.chunk(2)
sliding(window, step)
Creates sliding windows.
#persons = [{'name':'A'},{'name':'B'},{'name':'C'},{'name':'D'},{'name':'E'}]
#persons.sliding(3, 2)
forEach(#var, expr...)
Explanation
- First argument must be a variable reference.
- All following expressions run for each element.
- Result of the last expression builds the output list.
- Supports
#index.
Example
#persons = [
{'name':'Alice','age':30},
{'name':'Bob', 'age':40}
]
#persons.forEach(
#p,
{'index': #index, 'label': #p.name + ' (' + #p.age + ')'}
)
Native Java ArrayList Methods
In tSM SpEL, list literals like:
#list = [1, 2, 3]
are implemented as java.util.ArrayList. That means you can call many native Java methods directly on list variables.
However:
- Some methods are very useful in SpEL (e.g.
add,remove,contains, …). - Others are low-level JVM details (e.g.
ensureCapacity,trimToSize) with no visible effect in SpEL. - Some require Java 8 functional interfaces (
Predicate,UnaryOperator, …) and are not practical to use from SpEL expressions.
This section documents recommended native methods with examples.
Important note: initialize the list first
Native methods operate on the actual list instance.
If the variable is not initialized, it is null and you will get an error:
#list.add(1) // ERROR: cannot call add on null
Always initialize an empty list before first use:
#list = [] // creates new java.util.ArrayList
After that, all examples below will work as expected.
Methods
These methods are simple, predictable and genuinely useful in tSM SpEL.
size() – number of elements
Returns the number of elements in the list.
#list = [10, 20, 30]
#list.size()
Result: 3
isEmpty() – check if list is empty
Returns true if the list contains no elements.
#list = []
#list.isEmpty()
Result: true
get(index) – get element by index
Returns the element at the given index (0-based). Throws an error if index is out of range.
#list = [
{'name':'Alice'},
{'name':'Bob'},
{'name':'Carol'}
]
#list.get(1)
Result:
{'name':'Bob'}
add(element) – append element to the end
Adds a new element at the end of the list.
Typical use: dynamically building a list in a script.
#list = []
#list.add({'name':'Alice'})
#list.add({'name':'Bob'})
#list
Result:
[
{'name':'Alice'},
{'name':'Bob'}
]
add(index, element) – insert at position
Inserts an element at a specific position and shifts following elements to the right.
#list = [
{'name':'Alice'},
{'name':'Carol'}
]
#list.add(1, {'name':'Bob'})
#list
Result:
[
{'name':'Alice'},
{'name':'Bob'},
{'name':'Carol'}
]
addAll(otherList) – append multiple elements
Appends all elements from another collection to the end of the list.
#admins = [
{'name':'Alice', 'role':'ADMIN'}
]
#users = [
{'name':'Bob', 'role':'USER'},
{'name':'Carol', 'role':'USER'}
]
#admins.addAll(#users)
#admins
Result:
[
{'name':'Alice', 'role':'ADMIN'},
{'name':'Bob', 'role':'USER'},
{'name':'Carol', 'role':'USER'}
]
remove(index) – remove by index
Removes the element at the specified index and returns it. In the console you usually ignore the return value and inspect the modified list.
#list = [
{'name':'Alice'},
{'name':'Bob'},
{'name':'Carol'}
]
#list.remove(1)
#list
Result:
[
{'name':'Alice'},
{'name':'Carol'}
]
remove(element) – remove first occurrence
Removes the first occurrence of the given value from the list.
This is most reliable with simple values (numbers, strings, enums).
#roles = ['ADMIN','USER','GUEST','USER']
#roles.remove('USER')
#roles
Result:
['ADMIN','GUEST','USER']
For map/object values, equality depends on how the underlying type implements equals.
For most SpEL literals, it is safer to use tSM helpers like find / none / selection (.?[...]) instead.
clear() – remove all elements
Removes all elements from the list, making it empty.
#list = [
{'name':'Alice'},
{'name':'Bob'}
]
#list.clear()
#list
Result:
[]
contains(element) – check if value is present
Returns true if the list contains the value.
Best for simple types (strings, numbers, enums):
#roles = ['ADMIN','USER','GUEST']
#roles.contains('USER')
Result: true
For complex maps, prefer logical methods:
#persons = [
{'name':'Alice'},
{'name':'Bob'}
]
#persons.any(#this.name == 'Alice') // recommended
indexOf(element) – first index of value
Returns the index of the first occurrence of element or -1 if not found.
#list = [10, 20, 30, 20]
#list.indexOf(20)
Result: 1
lastIndexOf(element) – last index of value
Returns the index of the last occurrence of element or -1 if not found.
#list = [10, 20, 30, 20]
#list.lastIndexOf(20)
Result: 3
subList(fromIndex, toIndex) – view of a slice
Returns a view of the list between fromIndex (inclusive) and toIndex (exclusive).
#list = [
{'id':1},
{'id':2},
{'id':3},
{'id':4}
]
#list.subList(1, 3)
Result:
[
{'id':2},
{'id':3}
]
Note: this is a view backed by the original ArrayList. Changes in the base list can affect the sublist and vice versa.
clone() and toArray() – advanced / niche usage
These methods work but are typically used only in advanced scenarios.
clone()
Creates a shallow copy of the list (elements themselves are not cloned).
#list = [1, 2, 3]
#copy = #list.clone()
#copy
Result: a new list [1, 2, 3].
toArray()
Creates a Java Object[] array from the list.
Sometimes useful when passing data into an API that expects an array, not a list.
#list = [10, 20, 30]
#arr = #list.toArray()
#arr
Output format depends on how your tools render Java arrays.
Recommended mental model
For list operations in tSM SpEL, think in layers:
-
Core SpEL operators
list.?[...]for filtering,list.![...]for mapping → functional, safe, no side effects. -
tSM list extensions
sum,average,groupBy,toMap,flatMap,mapNotNull,any,all,none,find,findIndex,chunk,sliding,forEach,filter→ high-level helpers for real business logic. -
Native ArrayList methods (this chapter)
add,addAll,remove,clear,contains,indexOf,lastIndexOf,subList,size, … → low-level mutable tools when you need to build or tweak a list manually.