Groovy Spread Operator (*.) – Apply to All Elements with Examples

The Groovy spread operator (*.) lets you call methods and access properties on every element in a collection. 10+ tested examples with output on Groovy 5.0.6.

“Why write a loop when a single operator can do the job? The spread operator is Groovy’s answer to ‘apply this to everything.'”

Last Updated: July 2026 | Tested on: Groovy 5.0.6, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 15 minutes

How many times have you written a loop or a collect closure just to call the same method on every element in a list? In Java, that is the only way. In Groovy, you have the Groovy spread operator (*.), which does exactly that in a single, elegant expression.

The spread operator lets you invoke a method or access a property on every element in a collection, returning a new list of results. Instead of names.collect { it.toUpperCase() }, you simply write names*.toUpperCase(). Same result, less code, more expressive. It is one of those features that makes Groovy code feel like it reads your mind.

In this post, we will walk through 10+ tested examples of the spread operator applied to strings, objects, nested properties, method arguments, and real-world scenarios. If you are also working with null values in your collections, make sure to check out the Groovy Elvis operator and the Groovy safe navigation operator for the complete null-handling toolkit.

The diagram below shows how a spread-dot expression is evaluated step by step: what happens when the collection itself is null, how each element is visited, how null elements pass through safely as null results, and why the result always comes back as an ArrayList:

YesNoYesNoUse findAll { it != null}only if you need nullsgonecollection*.memberIs the collectionitself nullReturns nullno iteration happensIterate each elementleft to rightIs the currentelement nullResult for that element isnull(spread-dot skips nulls,like collect { it?.member })Call methodor read propertyAppend each resultto a new listReturn an ArrayListregardless of input typeGroovy Spread Operator: Evaluation Flow and Null Handling

What is the Spread Operator?

The Groovy spread operator (*.) is also called the spread-dot operator. It calls a method or accesses a property on each element in a collection and returns a list of results. Think of it as a shortcut for collect when you just need to call a single method or access a single property.

According to the official Groovy operators documentation, the spread operator is part of Groovy’s rich set of collection manipulation tools that make working with lists and arrays concise and readable.

Groovy actually has two related spread features:

  • Spread-dot operator (*.): Calls a method or accesses a property on each element — list*.method()
  • Spread operator (*): Unpacks a collection into individual arguments — method(*list)

We will cover both in this post, with the spread-dot operator being the primary focus since it is used far more frequently.

Spread Operator Syntax

Let us look at the syntax with a simple comparison to the traditional approach.

Spread Operator Syntax

def names = ["alice", "bob", "charlie"]

// Traditional: collect with closure
def upper1 = names.collect { it.toUpperCase() }

// Spread-dot: same result, shorter syntax
def upper2 = names*.toUpperCase()

// Both produce: [ALICE, BOB, CHARLIE]
assert upper1 == upper2

// Property access works too
class Person { String name; int age }
def people = [new Person(name: "Alice", age: 30), new Person(name: "Bob", age: 25)]

// Traditional
def names1 = people.collect { it.name }

// Spread-dot
def names2 = people*.name

// Both produce: [Alice, Bob]
assert names1 == names2

The pattern is collection*.methodOrProperty. Groovy iterates through the collection, calls the method or accesses the property on each element, and collects the results into a new list.

10+ Practical Examples

Let us work through real examples you can copy and run. Every example has been tested on Groovy 5.0.6 with actual output shown.

Example 1: Spread-Dot on String Methods

The most common use of the spread operator: calling string methods on every element in a list.

Example 1: Spread-Dot on Strings

def words = ["hello", "world", "groovy"]

// Call toUpperCase() on every element
def upper = words*.toUpperCase()
println "Upper: ${upper}"

// Call length() on every element
def lengths = words*.length()
println "Lengths: ${lengths}"

// Call capitalize() on every element
def capitalized = words*.capitalize()
println "Capitalized: ${capitalized}"

// Call reverse() on every element
def reversed = words*.reverse()
println "Reversed: ${reversed}"

// Call trim() on padded strings
def padded = ["  hello  ", " world ", "  groovy"]
def trimmed = padded*.trim()
println "Trimmed: ${trimmed}"

Output

Upper: [HELLO, WORLD, GROOVY]
Lengths: [5, 5, 6]
Capitalized: [Hello, World, Groovy]
Reversed: [olleh, dlrow, yvoorg]
Trimmed: [hello, world, groovy]

Any method that the elements support can be called with the spread-dot operator. It works the same as collect { it.method() } but is shorter and often more readable for single method calls.

Example 2: Spread-Dot for Property Access

The spread operator excels at extracting a single property from a list of objects. This is the pattern you will use the most in Groovy.

Example 2: Spread-Dot for Properties

class Employee {
    String name
    String department
    int salary
}

def employees = [
    new Employee(name: "Alice", department: "Engineering", salary: 95000),
    new Employee(name: "Bob", department: "Marketing", salary: 75000),
    new Employee(name: "Charlie", department: "Engineering", salary: 105000),
    new Employee(name: "Diana", department: "Marketing", salary: 80000)
]

// Extract single property from all objects
def names = employees*.name
println "Names: ${names}"

def departments = employees*.department
println "Departments: ${departments}"

def salaries = employees*.salary
println "Salaries: ${salaries}"

// Use extracted data for calculations
println "\nTotal payroll: ${salaries.sum()}"
println "Average salary: ${salaries.sum() / salaries.size()}"
println "Unique departments: ${departments.unique()}"
println "Highest salary: ${salaries.max()}"

Output

Names: [Alice, Bob, Charlie, Diana]
Departments: [Engineering, Marketing, Engineering, Marketing]
Salaries: [95000, 75000, 105000, 80000]

Total payroll: 355000
Average salary: 88750
Unique departments: [Engineering, Marketing]
Highest salary: 105000

The employees*.name pattern is the idiomatic Groovy way to extract a column of data from a list of objects. You will see this everywhere in Grails controllers, services, and test code.

Example 3: Spread-Dot with Maps

The spread operator works with maps too, operating on the map entries. You can also spread-dot into map values within a list of maps.

Example 3: Spread-Dot with Maps

// List of maps (common when working with JSON data)
def users = [
    [name: "Alice", age: 30, city: "London"],
    [name: "Bob", age: 25, city: "Paris"],
    [name: "Charlie", age: 35, city: "Tokyo"]
]

// Extract values by key using spread-dot with property-style access
def names = users*.name
println "Names: ${names}"

def cities = users*.city
println "Cities: ${cities}"

def ages = users*.age
println "Ages: ${ages}"

// Spread-dot with map's keySet and values
def singleMap = [a: 1, b: 2, c: 3]
def keys = singleMap.keySet()*.toUpperCase()
println "\nUppercase keys: ${keys}"

def values = singleMap.values()*.multiply(10)
println "Values x10: ${values}"

// Spread-dot on map entries
def entries = singleMap.collect { k, v -> "${k}=${v}" }
println "Entries: ${entries}"

Output

Names: [Alice, Bob, Charlie]
Cities: [London, Paris, Tokyo]
Ages: [30, 25, 35]

Uppercase keys: [A, B, C]
Values x10: [10, 20, 30]
Entries: [a=1, b=2, c=3]

Working with a list of maps is extremely common in Groovy, especially when dealing with parsed JSON responses. The spread-dot operator makes extracting a specific field from all records a one-liner.

Example 4: Spread-Dot with Method Arguments

You can pass arguments to the spread-dot method call, just like a normal method invocation.

Example 4: Spread-Dot with Method Arguments

def strings = ["Hello World", "Groovy Rocks", "Spread Operator"]

// Call replaceAll() on every string
def dashed = strings*.replaceAll(' ', '-')
println "Dashed: ${dashed}"

// Call substring() on every string
def first5 = strings*.take(5)
println "First 5 chars: ${first5}"

// Call padRight() on every string
def padded = ["cat", "elephant", "dog"]*.padRight(10, '.')
println "Padded: ${padded}"

// Call split() on every string
def words = ["a,b,c", "d,e,f", "g,h,i"]*.split(',')
println "Split: ${words}"

// Numbers: multiply each
def numbers = [1, 2, 3, 4, 5]
def doubled = numbers*.multiply(2)
println "\nDoubled: ${doubled}"

// Call plus() on each
def incremented = numbers*.plus(10)
println "Plus 10: ${incremented}"

Output

Dashed: [Hello-World, Groovy-Rocks, Spread-Operator]
First 5 chars: [Hello, Groov, Sprea]
Padded: [cat......., elephant.., dog.......]
Split: [[a, b, c], [d, e, f], [g, h, i]]

Doubled: [2, 4, 6, 8, 10]
Plus 10: [11, 12, 13, 14, 15]

Any method with any number of arguments works with the spread-dot operator. The arguments are passed identically to each element’s method call.

Example 5: Spread Operator for Unpacking Arguments (*)

Groovy also has the spread operator (*) for unpacking a collection into individual method arguments. This is different from the spread-dot (*.) but equally useful.

Example 5: Spread Operator for Argument Unpacking

// Define a method that takes individual parameters
def greet(String first, String last, String greeting) {
    return "${greeting}, ${first} ${last}!"
}

// Call with a list, spreading it into arguments
def args = ["Alex", "Johnson", "Hello"]
println greet(*args)

// Mixing spread with regular arguments
def nameArgs = ["Alice", "Smith"]
println greet(*nameArgs, "Hi")

// Spread into list construction
def first = [1, 2, 3]
def second = [4, 5, 6]
def combined = [*first, *second]
println "\nCombined: ${combined}"

// Spread into list with extra elements
def extended = [0, *first, 99, *second, 100]
println "Extended: ${extended}"

// Spread into map construction
def defaults = [host: "localhost", port: 8080]
def overrides = [port: 3000, debug: true]
def config = [*:defaults, *:overrides]
println "\nConfig: ${config}"

// The later spread wins for duplicate keys
def base = [a: 1, b: 2, c: 3]
def patch = [b: 20, d: 40]
def merged = [*:base, *:patch]
println "Merged: ${merged}"

Output

Hello, Alex Johnson!
Hi, Alice Smith!

Combined: [1, 2, 3, 4, 5, 6]
Extended: [0, 1, 2, 3, 99, 4, 5, 6, 100]

Config: [host:localhost, port:3000, debug:true]
Merged: [a:1, b:20, c:3, d:40]

The map spread ([*:map1, *:map2]) is particularly useful for merging configuration maps. The last spread wins for duplicate keys, which is exactly what you want for an “overrides” pattern. This is a common idiom in Gradle build scripts and Grails configuration.

Example 6: Spread-Dot with Null Elements

What happens when your list contains null elements? The spread-dot operator handles this differently than you might expect.

Example 6: Spread-Dot with Null Elements

// List with null elements
def names = ["Alice", null, "Charlie", null, "Eve"]

// Spread-dot is null-safe per element: nulls pass through as null
def upper = names*.toUpperCase()
println "Spread with nulls: ${upper}"

// Want the nulls gone? Filter first
def safeUpper = names.findAll { it != null }*.toUpperCase()
println "Filtered: ${safeUpper}"

// collect with safe navigation does the same as spread-dot here
def withNulls = names.collect { it?.toUpperCase() }
println "With collect: ${withNulls}"

// Spread-dot on a null list itself returns null
List nullList = null
def result = nullList*.toUpperCase()
println "\nNull list spread: ${result}"

// Property access also passes nulls through
class Person { String name }
def people = [new Person(name: "Alice"), null, new Person(name: "Charlie")]

def personNames = people*.name
println "Person names: ${personNames}"

// Drop the nulls if you need a clean list
def cleanNames = people.findAll()*.name
println "Clean names: ${cleanNames}"

Output

Spread with nulls: [ALICE, null, CHARLIE, null, EVE]
Filtered: [ALICE, CHARLIE, EVE]
With collect: [ALICE, null, CHARLIE, null, EVE]

Null list spread: null
Person names: [Alice, null, Charlie]
Clean names: [Alice, Charlie]

This is the most important gotcha with the Groovy spread operator: null elements inside the list pass through as null in the result: spread-dot skips the call for null elements instead of throwing. Use findAll() first when you want the nulls dropped rather than carried through.

Example 7: Spread-Dot for Nested Property Access

You can chain the spread-dot operator to access nested properties, though the syntax needs a bit of care.

Example 7: Nested Spread-Dot

class Address {
    String city
    String country
}

class Person {
    String name
    Address address
}

def people = [
    new Person(name: "Alice", address: new Address(city: "London", country: "UK")),
    new Person(name: "Bob", address: new Address(city: "Paris", country: "France")),
    new Person(name: "Charlie", address: new Address(city: "Tokyo", country: "Japan"))
]

// Extract nested property: first spread to get addresses, then spread to get cities
def cities = people*.address*.city
println "Cities: ${cities}"

def countries = people*.address*.country
println "Countries: ${countries}"

// You can also do it in one shot with collect
def citiesAlt = people.collect { it.address.city }
println "Cities (collect): ${citiesAlt}"

// Spread-dot with transformation
def upperCities = people*.address*.city*.toUpperCase()
println "Upper cities: ${upperCities}"

// Extract and process
def cityLengths = people*.address*.city*.length()
println "City name lengths: ${cityLengths}"

Output

Cities: [London, Paris, Tokyo]
Countries: [UK, France, Japan]
Cities (collect): [London, Paris, Tokyo]
Upper cities: [LONDON, PARIS, TOKYO]
City name lengths: [6, 5, 5]

Chaining *.address*.city first extracts all addresses into a list, then extracts all cities from that list. Each *. operates on the result of the previous one. This is incredibly powerful for drilling into nested data structures.

Example 8: Spread-Dot with Arrays and Ranges

The spread operator is not limited to lists. It works on arrays, ranges, and any iterable.

Example 8: Spread-Dot with Arrays and Ranges

// Spread on an array
String[] nameArray = ["alice", "bob", "charlie"]
def upperArray = nameArray*.toUpperCase()
println "Array spread: ${upperArray}"
println "Result type: ${upperArray.getClass().simpleName}"

// Spread on a range
def range = (1..5)
def doubled = range*.multiply(2)
println "\nRange doubled: ${doubled}"

// Spread on a set
def uniqueWords = ["hello", "world", "hello", "groovy"] as Set
def upperSet = uniqueWords*.toUpperCase()
println "Set spread: ${upperSet}"

// Spread on characters of a string (string as a list of chars)
def chars = "hello".toList()*.toUpperCase()
println "Char spread: ${chars}"

// Spread on a range of strings
def letters = ('a'..'f')*.toUpperCase()
println "Letter range: ${letters}"

// Spread on nested arrays (flattening first)
def nested = [[1, 2], [3, 4], [5, 6]]
def sizes = nested*.size()
println "\nNested sizes: ${sizes}"

def flattened = nested.flatten()*.multiply(10)
println "Flattened x10: ${flattened}"

Output

Array spread: [ALICE, BOB, CHARLIE]
Result type: ArrayList

Range doubled: [2, 4, 6, 8, 10]
Set spread: [HELLO, WORLD, GROOVY]
Char spread: [H, E, L, L, O]
Letter range: [A, B, C, D, E, F]

Nested sizes: [2, 2, 2]
Flattened x10: [10, 20, 30, 40, 50, 60]

Note that the spread-dot operator always returns an ArrayList, regardless of the input type. Even if you start with a Set or an array, the result is always a list.

Example 9: Spread-Dot Chained with Collection Operations

The spread-dot operator chains beautifully with other Groovy collection methods like sort, unique, findAll, and more.

Example 9: Spread-Dot Chained with Collection Ops

class Product {
    String name
    String category
    double price
}

def products = [
    new Product(name: "Laptop", category: "Electronics", price: 999.99),
    new Product(name: "Mouse", category: "Electronics", price: 29.99),
    new Product(name: "Desk", category: "Furniture", price: 249.99),
    new Product(name: "Chair", category: "Furniture", price: 399.99),
    new Product(name: "Monitor", category: "Electronics", price: 549.99)
]

// Spread + sort
def sortedNames = products*.name.sort()
println "Sorted names: ${sortedNames}"

// Spread + unique
def categories = products*.category.unique()
println "Categories: ${categories}"

// Spread + sum
def totalValue = products*.price.sum()
println "Total value: ${totalValue}"

// Filter first, then spread
def electronics = products.findAll { it.category == "Electronics" }
def electronicNames = electronics*.name
def electronicPrices = electronics*.price
println "\nElectronics: ${electronicNames}"
println "Electronics total: ${electronicPrices.sum()}"

// Spread + join for display
def priceList = products.collect { "${it.name}: \$${it.price}" }.join(' | ')
println "\nPrice list: ${priceList}"

// Sort by price, then get names
def byPrice = products.sort { it.price }*.name
println "Cheapest first: ${byPrice}"

Output

Sorted names: [Chair, Desk, Laptop, Monitor, Mouse]
Categories: [Electronics, Furniture]
Total value: 2229.95

Electronics: [Laptop, Mouse, Monitor]
Electronics total: 1579.97

Price list: Laptop: $999.99 | Mouse: $29.99 | Desk: $249.99 | Chair: $399.99 | Monitor: $549.99
Cheapest first: [Mouse, Desk, Chair, Monitor, Laptop]

The products*.name.sort() pattern is clean and readable: “get all product names, then sort them.” Each operation flows naturally into the next.

Example 10: Spread Operator for Constructor Calls and Builder Patterns

The spread operator (*) is useful for unpacking arguments into constructors and builder method calls.

Example 10: Spread in Constructors and Builders

// Spread into method calls
def sum(int a, int b, int c) { return a + b + c }
def numbers = [10, 20, 30]
println "Sum: ${sum(*numbers)}"

// Spread with varargs
def log(String level, String... messages) {
    messages.each { println "[${level}] ${it}" }
}

def errors = ["File not found", "Permission denied"]
log("ERROR", *errors)

// Spread for list construction from multiple sources
def admins = ["Alice", "Bob"]
def users = ["Charlie", "Diana"]
def guests = ["Eve"]

def allPeople = [*admins, *users, *guests]
println "\nAll people: ${allPeople}"

// Spread for map merging (configuration layering)
def defaults = [timeout: 30, retries: 3, verbose: false]
def envConfig = [timeout: 60, verbose: true]
def cliArgs = [retries: 5]

def finalConfig = [*:defaults, *:envConfig, *:cliArgs]
println "Final config: ${finalConfig}"

// Building dynamic method arguments
def formatDate(int year, int month, int day) {
    return "${year}-${String.format('%02d', month)}-${String.format('%02d', day)}"
}

def dateParts = [2026, 3, 8]
println "\nDate: ${formatDate(*dateParts)}"

Output

Sum: 60
[ERROR] File not found
[ERROR] Permission denied

All people: [Alice, Bob, Charlie, Diana, Eve]
Final config: [timeout:60, retries:5, verbose:true]

Date: 2026-03-08

The argument unpacking spread (*list) and the map spread ([*:map]) are distinct from the spread-dot (*.), but they are all part of the same family of spread operations in Groovy.

Example 11: Spread-Dot with GStrings and toString()

Using spread-dot to convert objects to strings and build display output is a common pattern.

Example 11: Spread-Dot with Strings and Display

class Task {
    String title
    String status
    String toString() { "[${status}] ${title}" }
}

def tasks = [
    new Task(title: "Write blog post", status: "DONE"),
    new Task(title: "Review PR", status: "IN_PROGRESS"),
    new Task(title: "Deploy to staging", status: "TODO"),
    new Task(title: "Run tests", status: "DONE")
]

// Spread-dot to get string representations
def taskStrings = tasks*.toString()
println "Tasks:"
taskStrings.each { println "  ${it}" }

// Extract and format specific fields
def titles = tasks*.title
def statuses = tasks*.status

// Filter by status using spread
def doneTasks = tasks.findAll { it.status == "DONE" }*.title
println "\nCompleted: ${doneTasks}"

def pendingTasks = tasks.findAll { it.status != "DONE" }*.title
println "Pending: ${pendingTasks}"

// Build a summary with spread
def statusCounts = tasks*.status.countBy { it }
println "\nStatus summary: ${statusCounts}"

// Join spread results for display
println "All titles: ${tasks*.title.join(', ')}"

Output

Tasks:
  [DONE] Write blog post
  [IN_PROGRESS] Review PR
  [TODO] Deploy to staging
  [DONE] Run tests

Completed: [Write blog post, Run tests]
Pending: [Review PR, Deploy to staging]

Status summary: [DONE:2, IN_PROGRESS:1, TODO:1]
All titles: Write blog post, Review PR, Deploy to staging, Run tests

The tasks*.status.countBy { it } pattern is a beautiful one-liner for generating frequency counts. Extract the field with spread-dot, then use countBy to tally.

Spread Operator vs collect()

Since both the spread-dot and collect() produce a list of transformed values, when should you use each one?

Spread-Dot vs collect()

def names = ["alice", "bob", "charlie"]

// SPREAD-DOT: best for single method call or property access
def upper1 = names*.toUpperCase()
def lens1 = names*.length()

// COLLECT: best for complex transformations
def upper2 = names.collect { it.toUpperCase() }
def formatted = names.collect { "${it.capitalize()} (${it.length()} chars)" }

// When to use spread-dot:
// 1. Single method call:       list*.method()
// 2. Single property access:   list*.property
// 3. Simple chained access:    list*.prop1*.prop2

// When to use collect:
// 1. Complex transformations:  list.collect { complex logic }
// 2. Multiple operations:      list.collect { it.trim().toUpperCase() }
// 3. Conditional logic:        list.collect { it.length() > 3 ? it : it.padRight(4) }

println "Spread: ${upper1}"
println "Collect: ${upper2}"
println "Complex: ${formatted}"

// Performance: both are equivalent
// Spread-dot compiles to essentially the same bytecode as collect
// Choose based on readability, not performance

Output

Spread: [ALICE, BOB, CHARLIE]
Collect: [ALICE, BOB, CHARLIE]
Complex: [Alice (5 chars), Bob (3 chars), Charlie (7 chars)]

Rule of thumb: Use spread-dot (*.) for simple, single-operation transformations. Use collect() when the transformation involves multiple steps, conditionals, or complex logic. Both produce the same result type and have equivalent performance.

Real-World Use Cases

Use Case 1: Processing API Responses

When you receive a list of objects from an API, the spread operator makes data extraction trivial.

Real-World: API Response Processing

// Simulated API response (parsed JSON)
def apiResponse = [
    [id: 1, title: "Introduction to Groovy", author: "Alice", tags: ["groovy", "beginner"]],
    [id: 2, title: "Advanced Closures", author: "Bob", tags: ["groovy", "closures"]],
    [id: 3, title: "Grails REST APIs", author: "Alice", tags: ["grails", "rest"]],
    [id: 4, title: "Spock Testing", author: "Charlie", tags: ["groovy", "testing"]]
]

// Quick data extraction
def titles = apiResponse*.title
println "Titles: ${titles}"

def authors = apiResponse*.author.unique().sort()
println "Authors: ${authors}"

def allTags = apiResponse*.tags.flatten().unique().sort()
println "All tags: ${allTags}"

// Filter and extract
def alicePosts = apiResponse.findAll { it.author == "Alice" }*.title
println "\nAlice's posts: ${alicePosts}"

def groovyPosts = apiResponse.findAll { "groovy" in it.tags }*.title
println "Groovy posts: ${groovyPosts}"

// Build a tag-to-post index
def tagIndex = [:]
apiResponse.each { post ->
    post.tags.each { tag ->
        tagIndex.getOrDefault(tag, [])
        if (!tagIndex[tag]) tagIndex[tag] = []
        tagIndex[tag] << post.title
    }
}
println "\nTag index:"
tagIndex.each { tag, posts -> println "  ${tag}: ${posts}" }

Output

Titles: [Introduction to Groovy, Advanced Closures, Grails REST APIs, Spock Testing]
Authors: [Alice, Bob, Charlie]
All tags: [beginner, closures, grails, groovy, rest, testing]

Alice's posts: [Introduction to Groovy, Grails REST APIs]
Groovy posts: [Introduction to Groovy, Advanced Closures, Spock Testing]

Tag index:
  groovy: [Introduction to Groovy, Advanced Closures, Spock Testing]
  beginner: [Introduction to Groovy]
  closures: [Advanced Closures]
  grails: [Grails REST APIs]
  rest: [Grails REST APIs]
  testing: [Spock Testing]

Use Case 2: CSV and Data Processing

The spread operator makes CSV parsing and data transformation clean and readable.

Real-World: CSV Data Processing

// Parse CSV-like data
def csvLines = [
    "  Alice , 30 , Engineering , 95000 ",
    "  Bob , 25 , Marketing , 75000 ",
    "  Charlie , 35 , Engineering , 105000 ",
    "  Diana , 28 , Marketing , 80000 "
]

// Parse each line: split, trim all fields
def records = csvLines.collect { line ->
    def fields = line.split(',')*.trim()
    [name: fields[0], age: fields[1].toInteger(),
     dept: fields[2], salary: fields[3].toInteger()]
}

println "Records:"
records.each { println "  ${it}" }

// Analytics using spread
def avgSalary = records*.salary.sum() / records.size()
println "\nAverage salary: ${avgSalary}"

def departments = records*.dept.unique()
println "Departments: ${departments}"

// Group and analyze
def byDept = records.groupBy { it.dept }
byDept.each { dept, members ->
    def avgPay = members*.salary.sum() / members.size()
    println "${dept}: ${members*.name} (avg: ${avgPay})"
}

Output

Records:
  [name:Alice, age:30, dept:Engineering, salary:95000]
  [name:Bob, age:25, dept:Marketing, salary:75000]
  [name:Charlie, age:35, dept:Engineering, salary:105000]
  [name:Diana, age:28, dept:Marketing, salary:80000]

Average salary: 88750
Departments: [Engineering, Marketing]
Engineering: [Alice, Charlie] (avg: 100000)
Marketing: [Bob, Diana] (avg: 77500)

The line.split(',')*.trim() pattern is the cleanest way to parse CSV fields in Groovy. Split by delimiter, trim every field in one shot. No loops, no temporary variables.

Edge Cases and Best Practices

Important Edge Cases

  • Null elements: [1, null, 3]*.toString() gives ["1", null, "3"], keeping the null in place. Filter with findAll() first if you want nulls gone.
  • Null collection: nullList*.method() returns null, not an empty list
  • Return type: The result is always an ArrayList, even if the input was a Set, array, or range
  • Performance: Equivalent to collect — no faster, no slower
  • Void methods: Spread-dot with void methods returns a list of nulls. Use each for side-effect-only operations instead.

Best Practices Summary

  • Use *. for single method calls or property access: list*.name
  • Use collect for complex transformations with multiple steps
  • Always handle null elements before using *. with methods that cannot be called on null
  • Use [*:map1, *:map2] for clean configuration merging
  • Chain *. with sort(), unique(), findAll() for powerful one-line data processing

Conclusion

The Groovy spread operator (*.) is one of those features that makes Groovy code so expressive compared to Java. From extracting property values from a list of objects to calling a method on every element or merging maps for configuration, the spread operator gives you a concise, readable way to do it.

Combined with the Elvis operator for defaults and the safe navigation operator for null safety, these three operators form the core of idiomatic Groovy programming. Master them, and your Groovy code will be dramatically shorter and easier to read.

Summary

  • list*.method() calls a method on every element, returning a list of results
  • list*.property extracts a property from every element
  • *list unpacks a list into individual method arguments
  • [*:map1, *:map2] merges maps with last-wins semantics
  • Watch out for null elements — spread-dot does not handle them automatically
  • Use collect when you need complex multi-step transformations

If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.

Up next: Groovy Spaceship Operator (<=>) – Compare Anything

Frequently Asked Questions

What is the spread operator in Groovy?

The Groovy spread operator (*.) is also called the spread-dot operator. It calls a method or accesses a property on every element in a collection and returns a list of results. For example, ['alice', 'bob']*.toUpperCase() returns ['ALICE', 'BOB']. It is a concise alternative to the collect() method for simple, single-operation transformations.

What is the difference between the spread operator (*.) and collect() in Groovy?

Both produce a list of transformed values. The spread operator (*.) is best for single method calls or property access (list*.name). The collect() method is better for complex transformations involving multiple steps, conditionals, or custom logic. Performance is identical — choose based on readability.

Does the Groovy spread operator handle null elements?

Yes, per element: spread-dot skips the call on null elements and puts null in the result list instead of throwing. Filter first with findAll { it != null } when you want the nulls dropped, or keep them when positions matter.

What happens when you use the spread operator on a null list in Groovy?

When the list itself is null, the spread-dot operator returns null (not an empty list). For example, if List items = null, then items*.name returns null. This is consistent with Groovy’s safe navigation behavior. If you need an empty list as default, add the Elvis operator: items*.name ?: [].

How do I merge maps using the spread operator in Groovy?

Use the map spread syntax: [*:map1, *:map2]. For example, [*:[a:1, b:2], *:[b:3, c:4]] produces [a:1, b:3, c:4]. The last spread wins for duplicate keys. This is commonly used for merging configuration defaults with overrides in Gradle build scripts and Grails applications.

Interview Questions on the Groovy Spread Operator

“These come from real screens. Practice answering before you read each answer.”

Q: In review you see someone replaced tempFiles.each { it.delete() } with tempFiles*.delete() “because it’s shorter.” It works. Do you approve it?

I’d push back. The spread operator is a collecting construct: it calls delete() on every element and builds a brand-new list of the boolean results, which this code immediately throws away. On a big temp directory that is pointless allocation, and it also misleads the reader, because *. signals “I want the transformed values” while each signals “I’m here for the side effects.” It behaves the same, but intent matters in review. The follow-up an interviewer wants: spread is sugar for collect { it?.delete() }, so a result list always gets built.

Q: A method takes a Set of role names, uppercases them with roles*.toUpperCase(), and suddenly a downstream uniqueness check starts failing. What changed?

The spread operator always returns a List, regardless of what collection type you spread over. So the Set went in, a List came out, and two roles like ‘admin’ and ‘Admin’ that were distinct in the Set both became ‘ADMIN’ entries in that List: duplicates the downstream code never expected. The type change is the quiet part; people assume the collection kind is preserved and it is not. If Set semantics matter, convert back deliberately, (roles*.toUpperCase()) as Set, and be aware that the conversion is where duplicates collapse.

Q: A teammate has def parts = ['api', 'v2'] and calls buildUrl(parts), but the method is declared as buildUrl(String base, String version) and they get a MissingMethodException. How does the spread operator fix this, and where does that trick stop working?

They passed one argument, a List, to a method that wants two Strings. The single-star spread unpacks the list into separate arguments at the call site: buildUrl(*parts) is exactly buildUrl('api', 'v2'). The limits: * only works inside argument lists and list literals, not in arbitrary expressions, and the unpacked count still has to match a real signature, so a three-element list fails the same way at runtime. Worth saying aloud in an interview: * unpacks arguments, *. maps a call over elements. Confusing the two is a classic mistake.

Q: You parse JSON with JsonSlurper and run records*.class to pull each record’s “class” field for a report. Instead of course codes you get java.util.LinkedHashMap over and over. Why?

Parsed JSON objects are maps, and property access on a map is a key lookup, except for a few names that collide with real Java methods. class is the worst offender: record.class resolves to getClass(), so you get the map’s own type, LinkedHashMap, not the value stored under the ‘class’ key. The spread just repeats that mistake for every element. The fix is explicit subscript access, records.collect { it['class'] }. The same caution applies to keys like metaClass. This one bites people constantly when reporting over JSON.

Q: A data job chains spreads over a few million parsed rows, something like rows*.normalize()*.checksum(), and the container is getting OOM-killed. What is your diagnosis and rewrite?

Every spread step is eager and materializes a complete new list, so that one line holds the original rows plus two full intermediate collections in memory at once. With millions of elements, that triples the footprint before any result is used. I would rewrite it as a single pass, rows.collect { it.normalize().checksum() }, which builds one result list instead of two, or go properly lazy with Java streams when the source is large or unbounded. The interview point: spread is convenient for small collections, but it has no laziness whatsoever.

Previous in Series: Groovy Safe Navigation Operator (?.) – Avoid NullPointerException

Next in Series: Groovy Spaceship Operator (<=>) – Compare Anything

Related Topics You Might Like:

This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *