Groovy’s collection syntax is one of its biggest wins over Java. Lists and maps have literals, operators and indexes that make working with data — especially JSON-shaped data — feel natural.
Lists
def skus = ['AX-200', 'BX-310'] // an ArrayList
skus << 'CX-420' // append
skus += 'DX-530'
skus[0] // AX-200
skus[-1] // DX-530 — last element
skus[1..2] // [BX-310, CX-420] — a slice
'BX-310' in skus // true
[3, 1, 2].sort() // [1, 2, 3]
[1, 2, 2, 3].unique() // [1, 2, 3]
[1, 2, 3] - [2] // [1, 3]
Maps
def stock = ['AX-200': 12, 'BX-310': 0] // a LinkedHashMap: keeps insertion order
stock['CX-420'] = 7
stock.'DX-530' = 3
stock.get('ZZ-999', 0) // 0 — and stores it as the default
def order = [id: 'PO-4471', customer: 'Acme'] // simple keys need no quotes
order.customer // Acme — property-style access
order.each { key, value -> println "$key -> $value" }
Watch out: [id: 'x'] uses the string 'id' as the key, not a variable called id. To use a variable’s value as a key, wrap it in parentheses: [(key): value].
An empty map is [:]; an empty list is [].
Ranges
(1..5).toList() // [1, 2, 3, 4, 5]
(1..<5).toList() // [1, 2, 3, 4] — exclusive end
('a'..'e').join('') // abcde
for (i in 1..3) { ... }
Ranges also work as list and string indexes, as you saw above.
Nested data and the spread operator
Real payloads are nested — maps inside lists inside maps, just like JSON:
def payload = [orders: [[id: 'PO-1', total: 250], [id: 'PO-2', total: 90]]]
payload.orders*.id // [PO-1, PO-2] — "id of every element"
payload.orders.total.sum() // 340 — property access on a list collects from every element
*. (spread-dot) calls a property or method on every element of a list. You’ll use it constantly with parsed JSON in Working with JSON and XML.
The full example
// collections.groovy — lists, maps and ranges with Groovy syntax.
// Lists: [] creates an ArrayList
def skus = ['AX-200', 'BX-310']
skus << 'CX-420' // append
skus += 'DX-530'
println skus // [AX-200, BX-310, CX-420, DX-530]
println skus[0] + ' ' + skus[-1] // first and last
println skus[1..2] // [BX-310, CX-420]
println skus.size()
println 'BX-310' in skus // true
println([3, 1, 2].sort()) // [1, 2, 3]
println([1, 2, 2, 3].unique()) // [1, 2, 3]
println([1, 2, 3] - [2]) // [1, 3]
// Maps: [key: value] creates a LinkedHashMap (keeps insertion order)
def stock = ['AX-200': 12, 'BX-310': 0]
stock['CX-420'] = 7 // add
stock.'DX-530' = 3 // property-style access
println stock // [AX-200:12, BX-310:0, CX-420:7, DX-530:3]
println stock['AX-200'] // 12
println stock.get('ZZ-999', 0) // 0 (and stores it)
def order = [id: 'PO-4471', customer: 'Acme', lines: 3] // simple keys need no quotes
println order.customer // Acme
order.each { key, value -> println "$key -> $value" }
// Ranges
def range = 1..5
println range.toList() // [1, 2, 3, 4, 5]
println((1..<5).toList()) // [1, 2, 3, 4] (exclusive end)
println(('a'..'e').join('')) // abcde
for (i in 1..3) print "$i "
println()
// Nested data, like JSON
def payload = [orders: [[id: 'PO-1', total: 250], [id: 'PO-2', total: 90]]]
println payload.orders*.id // [PO-1, PO-2] (spread operator)
println payload.orders.total.sum() // 340
Try it yourself
Given def lines = [[sku: 'AX-200', qty: 2], [sku: 'BX-310', qty: 5], [sku: 'AX-200', qty: 1]], build a map from SKU to total quantity.
Show a solution
def lines = [[sku: 'AX-200', qty: 2], [sku: 'BX-310', qty: 5], [sku: 'AX-200', qty: 1]]
def totals = [:]
lines.each { totals[it.sku] = (totals[it.sku] ?: 0) + it.qty }
println totals // [AX-200:3, BX-310:5]
In Functional collection methods you’ll do this with groupBy in one line.
Key takeaways
[]makes lists,[k: v]makes (ordered) maps;<<,+,-andinwork on them.- Negative indexes and ranges slice lists and strings.
- Map keys in literals are strings; use
(var)for a variable key. *.and property access on lists make nested data easy to navigate.