10 Practical Groovy findAll() Examples to Filter Collections

Groovy findAll() makes filtering collections effortless. Filter lists, maps, and strings with 10 tested examples. Complete filtering guide for Groovy 5.x.

“Filtering is the art of keeping only what matters. In Groovy, findAll() makes that art effortless.”

Venkat Subramaniam, Programming Groovy 2

Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 16 minutes

Filtering is one of the most common collection operations in any language. You have a list of items and you want only the ones that match some condition – all even numbers, all active users, all log lines containing “ERROR.” The groovy findAll method handles this with a single closure, returning a new collection of matching elements.

That is exactly what Groovy findAll() does. It takes a collection and a closure (a filtering condition), and returns a new collection containing only the elements that pass the test. If you have used filter() in JavaScript or filter() in Python, findAll() is the Groovy equivalent.

In our previous post, we looked at Groovy find() which returns only the first matching element. Now we are going the full distance with findAll(), which returns every matching element. You will also see how findAll() works beautifully with the each() loop and other GDK collection methods.

This tutorial gives you 10+ tested examples covering lists, maps, strings, regex matching, null filtering, object filtering, and real-world scenarios. Every example has been tested on Groovy 5.x so you can copy-paste them directly into your scripts.

What Is findAll() in Groovy?

findAll() is a GDK method available on all Groovy collections (lists, sets, maps) and even on strings. It iterates over each element, applies the closure you provide, and collects every element where the closure returns a truthy value into a new list.

According to the official Groovy GDK documentation, the method signature is:

findAll() Signature

// On Collection
public List findAll(Closure closure)

// On Map
public Map findAll(Closure closure)

// On String (regex)
public List findAll(CharSequence regex)
public List findAll(CharSequence regex, Closure closure)

Key Points:

  • findAll() returns a new collection – it never modifies the original
  • On lists and sets, it returns a List
  • On maps, it returns a Map
  • On strings, it returns a List of matched substrings
  • If no elements match, it returns an empty collection (not null)
  • The closure uses Groovy truthiness – null, 0, "", and empty collections are falsy

Syntax and Basic Usage

The basic pattern for findAll() is simple. You call it on a collection and pass a closure that returns true or false for each element.

Basic findAll() Syntax

def numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// Basic syntax: collection.findAll { condition }
def evens = numbers.findAll { it % 2 == 0 }
println evens

// With explicit parameter name
def odds = numbers.findAll { num -> num % 2 != 0 }
println odds

// Without a closure - removes falsy values
def mixed = [0, 1, '', 'hello', null, true, false, [], [1]]
def truthy = mixed.findAll()
println truthy

Output

[2, 4, 6, 8, 10]
[1, 3, 5, 7, 9]
[1, hello, true, [1]]

Notice that last example – calling findAll() without a closure filters out all falsy values. That is incredibly handy for cleaning up data. The values 0, '', null, false, and an empty list are all considered falsy in Groovy, so they get removed.

10 Practical findAll() Examples

Let us walk through 10 examples that cover every common use case for findAll(). Each example is self-contained, tested, and ready to run.

Example 1: findAll on Lists

What we’re doing: Filtering a list of numbers using various conditions.

Example 1: findAll on Lists

def numbers = [12, 5, 87, 34, 2, 99, 41, 68, 23, 56]

// Filter numbers greater than 50
def greaterThan50 = numbers.findAll { it > 50 }
println "Greater than 50: ${greaterThan50}"

// Filter numbers between 10 and 40
def between10And40 = numbers.findAll { it >= 10 && it <= 40 }
println "Between 10-40: ${between10And40}"

// Filter numbers divisible by 3
def divisibleBy3 = numbers.findAll { it % 3 == 0 }
println "Divisible by 3: ${divisibleBy3}"

// Filter using a range
def inRange = numbers.findAll { it in 20..60 }
println "In range 20..60: ${inRange}"

Output

Greater than 50: [87, 99, 68, 56]
Between 10-40: [12, 34, 23]
Divisible by 3: [12, 87, 99]
In range 20..60: [34, 41, 23, 56]

What happened here: Each call to findAll() creates a new list with only the matching elements. The original numbers list stays untouched. Notice how you can use the in operator with a range – that is a Groovy shortcut for checking if a value falls within a range.

Example 2: findAll on Maps

What we’re doing: Filtering map entries by key, value, or both.

Example 2: findAll on Maps

def scores = [Alice: 92, Bob: 67, Charlie: 85, Diana: 43, Eve: 78]

// Filter students who scored above 75
def passed = scores.findAll { name, score -> score >= 75 }
println "Passed: ${passed}"

// Filter by key (name starts with a vowel)
def vowelNames = scores.findAll { name, score ->
    name[0].toLowerCase() in ['a', 'e', 'i', 'o', 'u']
}
println "Vowel names: ${vowelNames}"

// Using entry instead of key-value
def failed = scores.findAll { entry -> entry.value < 50 }
println "Failed: ${failed}"

// Result is a Map, not a List
println "Type: ${passed.getClass().simpleName}"

Output

Passed: [Alice:92, Charlie:85, Eve:78]
Vowel names: [Alice:92, Eve:78]
Failed: [Diana:43]
Type: LinkedHashMap

What happened here: When you call findAll() on a Map, the closure receives either a Map.Entry or two parameters (key, value). The result is also a Map – not a List. This preserves the key-value structure, which is exactly what you want when filtering map data.

Example 3: findAll on Strings (Regex)

What we’re doing: Using findAll() with regular expressions to extract matching substrings.

Example 3: findAll on Strings (Regex)

def text = "Contact us at support@example.com or sales@example.com for help"

// Find all email addresses
def emails = text.findAll(/\b[\w.]+@[\w.]+\.\w+\b/)
println "Emails: ${emails}"

// Find all words starting with uppercase
def sentence = "The Quick Brown Fox Jumped Over the Lazy Dog"
def capitalWords = sentence.findAll(/\b[A-Z]\w*/)
println "Capital words: ${capitalWords}"

// Find all numbers in a string
def data = "Order #1234 has 5 items weighing 12.5 kg"
def allNumbers = data.findAll(/\d+\.?\d*/)
println "Numbers: ${allNumbers}"

// Find with capture groups
def html = '<a href="page1.html">Link 1</a> and <a href="page2.html">Link 2</a>'
def links = html.findAll(/href="([^"]+)"/) { full, group1 -> group1 }
println "Links: ${links}"

Output

Emails: [support@example.com, sales@example.com]
Capital words: [The, Quick, Brown, Fox, Jumped, Over, Lazy, Dog]
Numbers: [1234, 5, 12.5]
Links: []

What happened here: On strings, findAll() works as a regex matcher. It scans the entire string and returns a list of all substrings that match the pattern. When you pass a closure as the second argument, you get access to capture groups – the full match and each group. This is powerful for extracting structured data from text.

Example 4: findAll with Complex Conditions

What we’re doing: Combining multiple conditions inside the findAll closure.

Example 4: findAll with Complex Conditions

def words = ['groovy', 'java', 'kotlin', 'scala', 'go', 'rust', 'python', 'c']

// Multiple conditions: length > 3 AND contains 'o'
def result1 = words.findAll { it.length() > 3 && it.contains('o') }
println "Length > 3 and contains 'o': ${result1}"

// Either starts with vowel OR is exactly 2 characters
def result2 = words.findAll { word ->
    word[0] in ['a','e','i','o','u'] || word.length() == 2
}
println "Starts with vowel or length 2: ${result2}"

// Using a separate predicate closure
def isModernLanguage = { it in ['kotlin', 'rust', 'go'] }
def modern = words.findAll(isModernLanguage)
println "Modern languages: ${modern}"

// Negated condition - all words NOT in a blacklist
def blacklist = ['c', 'go'] as Set
def filtered = words.findAll { !(it in blacklist) }
println "Not in blacklist: ${filtered}"

Output

Length > 3 and contains 'o': [groovy, kotlin, python]
Starts with vowel or length 2: [go]
Modern languages: [kotlin, go, rust]
Not in blacklist: [groovy, java, kotlin, scala, rust, python]

What happened here: You can put any logic you want inside the findAll() closure – combine conditions with && and ||, call methods on each element, or even pass a predefined closure variable. The key is that whatever your closure returns gets evaluated for truthiness.

Example 5: findAll vs find

What we’re doing: Comparing findAll() with find() to understand when to use each one.

Example 5: findAll vs find

def numbers = [3, 7, 12, 5, 18, 9, 24, 6]

// find() - returns the FIRST match (single element)
def firstEven = numbers.find { it % 2 == 0 }
println "find() result: ${firstEven}"
println "find() type: ${firstEven.getClass().simpleName}"

// findAll() - returns ALL matches (list)
def allEvens = numbers.findAll { it % 2 == 0 }
println "findAll() result: ${allEvens}"
println "findAll() type: ${allEvens.getClass().simpleName}"

// When nothing matches
def findNone = numbers.find { it > 100 }
def findAllNone = numbers.findAll { it > 100 }
println "find() no match: ${findNone}"         // null
println "findAll() no match: ${findAllNone}"   // empty list

// Practical difference
def users = ['admin', 'editor', 'viewer', 'admin', 'viewer']
println "First admin: ${users.find { it == 'admin' }}"
println "All admins: ${users.findAll { it == 'admin' }}"
println "Admin count: ${users.findAll { it == 'admin' }.size()}"

Output

find() result: 12
find() type: Integer
findAll() result: [12, 18, 24, 6]
findAll() type: ArrayList
find() no match: null
findAll() no match: []
First admin: admin
All admins: [admin, admin]
Admin count: 2

What happened here: The critical difference is this – find() returns a single element (or null), while findAll() returns a list (or empty list). Use find() when you only care about the first match. Use findAll() when you need every match. Note that findAll() never returns null – an empty list is much safer to work with downstream.

Example 6: findAll vs grep

What we’re doing: Comparing findAll() with Groovy’s grep() method.

Example 6: findAll vs grep

def items = [1, 'hello', 3.14, null, 42, 'world', true, [1, 2]]

// grep with a Class - filter by type
def strings = items.grep(String)
println "grep(String): ${strings}"

// findAll equivalent - more verbose
def strings2 = items.findAll { it instanceof String }
println "findAll instanceof String: ${strings2}"

// grep with a regex pattern
def words = ['apple', 'banana', 'avocado', 'cherry', 'apricot']
def startsWithA = words.grep(~/a.*/)
println "grep regex a*: ${startsWithA}"

// findAll equivalent
def startsWithA2 = words.findAll { it ==~ /a.*/ }
println "findAll regex a*: ${startsWithA2}"

// grep with a range
def numbers = [5, 12, 3, 87, 45, 23, 99, 8]
def inRange = numbers.grep(10..50)
println "grep range 10..50: ${inRange}"

// findAll equivalent
def inRange2 = numbers.findAll { it in 10..50 }
println "findAll range 10..50: ${inRange2}"

// grep with a Collection (membership check)
def allowed = [5, 23, 99]
println "grep membership: ${numbers.grep(allowed)}"

Output

grep(String): [hello, world]
findAll instanceof String: [hello, world]
grep regex a*: [apple, avocado, apricot]
findAll regex a*: [apple, avocado, apricot]
grep range 10..50: [12, 45, 23]
findAll range 10..50: [12, 45, 23]
grep membership: [5, 23, 99]

What happened here: grep() uses Groovy’s isCase() method under the hood. It accepts classes, regex patterns, ranges, and collections directly – no closure needed. findAll() is more flexible since it takes a closure where you can write any condition. Use grep() for simple type or pattern matching, and findAll() when your filtering logic is more complex.

Example 7: Chaining findAll with collect

What we’re doing: Building data pipelines by chaining findAll() with collect() and other methods.

Example 7: Chaining findAll with collect

def employees = [
    [name: 'Alice', dept: 'Engineering', salary: 95000],
    [name: 'Bob', dept: 'Marketing', salary: 62000],
    [name: 'Charlie', dept: 'Engineering', salary: 110000],
    [name: 'Diana', dept: 'HR', salary: 58000],
    [name: 'Eve', dept: 'Engineering', salary: 88000],
    [name: 'Frank', dept: 'Marketing', salary: 71000]
]

// Filter then transform: get engineering salaries
def engSalaries = employees
    .findAll { it.dept == 'Engineering' }
    .collect { it.salary }
println "Engineering salaries: ${engSalaries}"

// Filter, transform, then aggregate
def avgEngSalary = employees
    .findAll { it.dept == 'Engineering' }
    .collect { it.salary }
    .average()
println "Average engineering salary: ${avgEngSalary}"

// Chain findAll + collect + sort
def highEarnerNames = employees
    .findAll { it.salary > 70000 }
    .collect { it.name }
    .sort()
println "High earners (sorted): ${highEarnerNames}"

// Multiple findAll in a chain
def result = (1..100)
    .findAll { it % 3 == 0 }    // divisible by 3
    .findAll { it % 5 == 0 }    // also divisible by 5
    .collect { "FizzBuzz(${it})" }
println "FizzBuzz numbers: ${result}"

Output

Engineering salaries: [95000, 110000, 88000]
Average engineering salary: 97666.6666666667
High earners (sorted): [Alice, Charlie, Eve, Frank]
FizzBuzz numbers: [FizzBuzz(15), FizzBuzz(30), FizzBuzz(45), FizzBuzz(60), FizzBuzz(75), FizzBuzz(90)]

What happened here: Chaining is where findAll() really shines. Since it returns a new collection, you can immediately call collect() to transform, sort() to order, sum() to aggregate, or even another findAll() to narrow down further. This is functional-style programming at its best, and Groovy makes it feel natural.

Example 8: findAll with Negation

What we’re doing: Inverting conditions to find elements that do NOT match.

Example 8: findAll with Negation

def fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry', 'fig']

// Find all fruits NOT starting with vowels
def noVowelStart = fruits.findAll { !(it[0] in ['a', 'e', 'i', 'o', 'u']) }
println "No vowel start: ${noVowelStart}"

// Find all elements NOT in a blacklist
def banned = ['banana', 'fig']
def allowed = fruits.findAll { !(it in banned) }
println "Allowed fruits: ${allowed}"

// Using minus operator as alternative
def allowed2 = fruits - banned
println "Using minus: ${allowed2}"

// Negate a regex match
def data = ['ABC-123', 'hello', 'XYZ-789', 'world', 'DEF-456']
def nonCodes = data.findAll { !(it ==~ /[A-Z]{3}-\d{3}/) }
println "Non-codes: ${nonCodes}"

// Double negation - findAll elements where a condition is NOT false
def values = [0, 1, -1, 2, -3, 5, -8]
def nonNegative = values.findAll { !(it < 0) }
println "Non-negative: ${nonNegative}"

Output

No vowel start: [banana, cherry, date, fig]
Allowed fruits: [apple, cherry, date, elderberry]
Using minus: [apple, cherry, date, elderberry]
Non-codes: [hello, world]
Non-negative: [0, 1, 2, 5]

What happened here: Negation with ! works perfectly inside findAll() closures. For simple exclusion of elements, Groovy’s minus operator (-) can be even cleaner. But when you need to negate a complex condition or a regex pattern, the findAll with negation approach is the way to go.

Example 9: Filtering Null and Empty Values

What we’re doing: Cleaning up collections by removing nulls, empty strings, and blank values.

Example 9: Filtering Null and Empty Values

// Remove nulls
def withNulls = ['apple', null, 'banana', null, 'cherry', null]
def noNulls = withNulls.findAll { it != null }
println "No nulls: ${noNulls}"

// Shorter: findAll() without closure removes falsy values
def noFalsy = withNulls.findAll()
println "No falsy: ${noFalsy}"

// Remove empty strings
def withEmpty = ['hello', '', 'world', '', '  ', 'groovy']
def noEmpty = withEmpty.findAll { it?.trim() }
println "No empty/blank: ${noEmpty}"

// Remove nulls from a map's values
def config = [host: 'localhost', port: 8080, db: null, user: 'admin', pass: null]
def validConfig = config.findAll { k, v -> v != null }
println "Valid config: ${validConfig}"

// Compact pattern: filter + transform
def rawData = [' Alice ', null, '', ' Bob ', '  ', null, ' Charlie ']
def cleanNames = rawData
    .findAll { it?.trim() }      // remove null, empty, blank
    .collect { it.trim() }        // trim whitespace
println "Clean names: ${cleanNames}"

Output

No nulls: [apple, banana, cherry]
No falsy: [apple, banana, cherry]
No empty/blank: [hello, world, groovy]
Valid config: [host:localhost, port:8080, user:admin]
Clean names: [Alice, Bob, Charlie]

What happened here: This is one of the most common real-world uses of findAll(). Data from databases, APIs, or CSV files often contains nulls and empty values. The safe navigation operator (?.) inside the closure prevents NullPointerException. The pattern of findAll { it?.trim() } is something you will use again and again – it removes nulls, empty strings, and whitespace-only strings in one go.

Example 10: Filtering Objects by Property

What we’re doing: Filtering a list of objects (maps and classes) based on their properties.

Example 10: Filtering Objects by Property

// Using maps as lightweight objects
def products = [
    [name: 'Laptop', price: 999, category: 'Electronics', inStock: true],
    [name: 'Book', price: 15, category: 'Education', inStock: true],
    [name: 'Phone', price: 699, category: 'Electronics', inStock: false],
    [name: 'Desk', price: 250, category: 'Furniture', inStock: true],
    [name: 'Tablet', price: 449, category: 'Electronics', inStock: true],
    [name: 'Chair', price: 180, category: 'Furniture', inStock: false]
]

// Filter by single property
def electronics = products.findAll { it.category == 'Electronics' }
println "Electronics: ${electronics.collect { it.name }}"

// Filter by multiple properties
def affordableInStock = products.findAll { it.price < 500 && it.inStock }
println "Affordable & in stock: ${affordableInStock.collect { it.name }}"

// Using a class
class User {
    String name
    int age
    boolean active
    String toString() { "${name}(${age})" }
}

def users = [
    new User(name: 'Alice', age: 28, active: true),
    new User(name: 'Bob', age: 35, active: false),
    new User(name: 'Charlie', age: 22, active: true),
    new User(name: 'Diana', age: 41, active: true)
]

// Filter active users over 25
def result = users.findAll { it.active && it.age > 25 }
println "Active users over 25: ${result}"

// Filter by property using spread operator for verification
println "All ages: ${users*.age}"
println "Active flags: ${users*.active}"

Output

Electronics: [Laptop, Phone, Tablet]
Affordable & in stock: [Book, Desk, Tablet]
Active users over 25: [Alice(28), Diana(41)]
All ages: [28, 35, 22, 41]
Active flags: [true, false, true, true]

What happened here: Maps or classes – findAll() handles them the same way. Groovy’s property access syntax (it.name) works on both maps and objects, so you can switch from maps to proper classes without changing your filtering code. The spread operator (*.) is great for quickly inspecting a single property across all elements.

findAll() vs find() vs grep()

These three methods are often confused. Here is a quick comparison to clear things up:

MethodReturnsNo MatchInputBest For
find()Single elementnullClosureFirst match only
findAll()List (or Map)Empty collectionClosureAll matches with custom logic
grep()ListEmpty listObject (class, regex, range)Simple type/pattern matching

The rule of thumb: use find() when you need one result, findAll() when you need all results with custom logic, and grep() when filtering by type, regex, or range without needing a closure.

Real-World Use Cases

Data Filtering Pipeline

Suppose you have a CSV-like dataset and need to filter, clean, and transform it:

Real-World: Data Filtering

// Simulated CSV data (header + rows)
def csvRows = [
    'Alice,Engineering,95000,active',
    'Bob,Marketing,62000,inactive',
    '',
    'Charlie,Engineering,110000,active',
    null,
    'Diana,HR,58000,active',
    'Eve,Engineering,88000,inactive',
    '  ',
    'Frank,Marketing,71000,active'
]

// Full pipeline: clean -> parse -> filter -> report
def activeEngineers = csvRows
    .findAll { it?.trim() }                          // remove null/empty/blank
    .collect { it.split(',') }                        // parse CSV
    .findAll { it[1] == 'Engineering' }              // filter by dept
    .findAll { it[3] == 'active' }                   // filter by status
    .collect { [name: it[0], salary: it[2] as int] } // transform to maps

println "Active Engineers:"
activeEngineers.each { println "  ${it.name}: \$${it.salary}" }
println "Total salary: \$${activeEngineers.collect { it.salary }.sum()}"

Output

Active Engineers:
  Alice: $95000
  Charlie: $110000
Total salary: $205000

Log Parsing

Extracting specific information from application logs is a common scripting task:

Real-World: Log Parsing

def logLines = [
    '2026-03-08 10:15:23 INFO  UserService - User login: alice',
    '2026-03-08 10:15:45 ERROR PaymentService - Payment failed: timeout',
    '2026-03-08 10:16:01 INFO  UserService - User login: bob',
    '2026-03-08 10:16:12 WARN  CacheService - Cache miss for key: user_123',
    '2026-03-08 10:16:30 ERROR PaymentService - Payment failed: invalid card',
    '2026-03-08 10:17:00 INFO  OrderService - Order placed: #4521',
    '2026-03-08 10:17:15 ERROR DBService - Connection pool exhausted'
]

// Find all ERROR lines
def errors = logLines.findAll { it.contains('ERROR') }
println "Errors (${errors.size()}):"
errors.each { println "  ${it}" }

// Extract error messages using regex findAll on each line
def errorMessages = logLines
    .findAll { it.contains('ERROR') }
    .collect { line -> line.findAll(/- (.+)/) { full, msg -> msg }[0] }
println "\nError messages: ${errorMessages}"

// Find all services that had errors
def errorServices = logLines
    .findAll { it.contains('ERROR') }
    .collect { it.findAll(/ERROR\s+(\w+)/){ full, svc -> svc }[0] }
    .unique()
println "Services with errors: ${errorServices}"

Output

Errors (3):
  2026-03-08 10:15:45 ERROR PaymentService - Payment failed: timeout
  2026-03-08 10:16:30 ERROR PaymentService - Payment failed: invalid card
  2026-03-08 10:17:15 ERROR DBService - Connection pool exhausted
Error messages: [Payment failed: timeout, Payment failed: invalid card, Connection pool exhausted]
Services with errors: [PaymentService, DBService]

Report Generation

Building summary reports by filtering and grouping data:

Real-World: Report Generation

def orders = [
    [id: 1001, customer: 'Alice', amount: 250.00, status: 'completed'],
    [id: 1002, customer: 'Bob', amount: 89.50, status: 'pending'],
    [id: 1003, customer: 'Alice', amount: 175.00, status: 'completed'],
    [id: 1004, customer: 'Charlie', amount: 320.00, status: 'cancelled'],
    [id: 1005, customer: 'Bob', amount: 450.00, status: 'completed'],
    [id: 1006, customer: 'Alice', amount: 95.00, status: 'pending'],
    [id: 1007, customer: 'Diana', amount: 600.00, status: 'completed']
]

// Report: completed orders summary
def completed = orders.findAll { it.status == 'completed' }
println "=== Completed Orders Report ==="
println "Total completed: ${completed.size()}"
println "Total revenue: \$${completed.collect { it.amount }.sum()}"
println "Average order: \$${completed.collect { it.amount }.average()}"

// Top customers (completed orders only)
def topCustomers = completed
    .groupBy { it.customer }
    .findAll { name, customerOrders -> customerOrders.size() > 1 }
    .collect { name, customerOrders ->
        [name: name, orders: customerOrders.size(),
         total: customerOrders.collect { it.amount }.sum()]
    }
println "\nRepeat customers:"
topCustomers.each { println "  ${it.name}: ${it.orders} orders, \$${it.total}" }

// Pending actions needed
def actionNeeded = orders.findAll { it.status in ['pending', 'cancelled'] }
println "\nAction needed: ${actionNeeded.collect { "#${it.id} (${it.status})" }}"

Output

=== Completed Orders Report ===
Total completed: 4
Total revenue: $1475.0
Average order: $368.75
Repeat customers:
  Alice: 2 orders, $425.0

Action needed: [#1002 (pending), #1004 (cancelled), #1006 (pending)]

These real-world examples show how findAll() becomes the backbone of data processing pipelines in Groovy. Combined with collect(), groupBy(), and sum(), you can build surprisingly powerful data transformations in just a few lines.

Edge Cases and Best Practices

Edge Case: findAll on an Empty Collection

Edge Cases

// Empty list - safe, returns empty list
def empty = [].findAll { it > 5 }
println "Empty findAll: ${empty}"
println "Is list: ${empty instanceof List}"

// findAll preserves order
def ordered = [5, 3, 8, 1, 9, 2, 7]
def filtered = ordered.findAll { it > 4 }
println "Order preserved: ${filtered}"

// findAll on a Set returns a List
def mySet = [1, 2, 3, 4, 5] as Set
def fromSet = mySet.findAll { it > 3 }
println "Set findAll type: ${fromSet.getClass().simpleName}"

// Closure that returns non-boolean truthy values
def items = ['', 'hello', null, 'world', 0, 42, [], [1]]
// Each element IS the return value - Groovy truthiness applies
def truthy = items.findAll { it }
println "Truthy items: ${truthy}"

Output

Empty findAll: []
Is list: true
Order preserved: [5, 8, 9, 7]
Set findAll type: ArrayList
Truthy items: [hello, world, 42, [1]]

Best Practices Summary

DO:

  • Use findAll() when you need all matching elements from a collection
  • Chain findAll() with collect() for filter-then-transform pipelines
  • Use findAll() without a closure to quickly remove all falsy values
  • Use the safe navigation operator (?.) inside closures when data may contain nulls
  • Remember that findAll() on a Map returns a Map, not a List

DON’T:

  • Use findAll() when you only need the first match – use find() instead
  • Use findAll() for simple type checking – use grep() instead
  • Modify the original collection inside a findAll() closure – it can cause ConcurrentModificationException
  • Use findAll() on very large datasets without considering stream() or lazy evaluation

Performance Considerations

For most use cases, findAll() performance is not a concern. However, there are a few things worth knowing when working with large datasets:

Performance Tips

def bigList = (1..100000).toList()

// findAll creates a new list - memory consideration
def filtered = bigList.findAll { it % 2 == 0 }
println "Filtered size: ${filtered.size()}"

// For very large data, consider Java streams
def streamResult = bigList.stream()
    .filter { it % 2 == 0 }
    .filter { it > 50000 }
    .collect(java.util.stream.Collectors.toList())
println "Stream result size: ${streamResult.size()}"

// Chaining multiple findAll creates intermediate lists
// Less efficient:
def result1 = bigList.findAll { it % 2 == 0 }.findAll { it > 50000 }

// More efficient - single pass:
def result2 = bigList.findAll { it % 2 == 0 && it > 50000 }

println "Both produce same result: ${result1 == result2}"
println "Result size: ${result2.size()}"

Output

Filtered size: 50000
Stream result size: 25000
Both produce same result: true
Result size: 25000

Each findAll() call creates a new list, so chaining multiple findAll() calls creates intermediate lists. For small to medium collections (under 10,000 elements), this is negligible. For large datasets, combine conditions into a single closure or use Java streams for lazy evaluation.

Conclusion

We covered Groovy findAll() from every angle in this tutorial – filtering lists, maps, and strings with regex, combining complex conditions, removing null values, filtering objects by property, and building real-world data pipelines. The method is one of the most frequently used in the Groovy GDK, and for good reason: it makes filtering feel effortless.

The pattern is always the same: collection.findAll { condition }. Groovy handles the iteration, the condition checking, and the new collection creation for you. Chain it with collect(), groupBy(), sort(), or sum() and you have a full data processing pipeline in just a few lines of code.

Summary

  • findAll() returns all matching elements as a new collection – never modifies the original
  • On maps, findAll() returns a Map; on lists and strings, it returns a List
  • Calling findAll() without a closure removes all falsy values (null, 0, “”, false, empty list)
  • On strings, findAll(regex) extracts all matching substrings – great for parsing
  • Use find() for the first match, findAll() for all matches, and grep() for type/pattern matching

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 Exception Handling – try-catch-finally

Frequently Asked Questions

What does findAll() return when no elements match?

findAll() returns an empty list (or empty map for map inputs) when no elements match the condition. It never returns null. This makes it safe to chain with other methods like collect(), size(), or each() without null checks.

What is the difference between findAll() and find() in Groovy?

find() returns the first single element that matches the condition (or null if nothing matches). findAll() returns a list of all elements that match. Use find() when you only need one result, and findAll() when you need every matching element.

Can I use findAll() on a Groovy map?

Yes. When called on a map, findAll() receives either a Map.Entry or two parameters (key, value) in the closure. It returns a new Map containing only the entries that match the condition, preserving the original key-value structure.

How does findAll() work on strings in Groovy?

On strings, findAll(regex) scans the entire string and returns a list of all substrings that match the regular expression pattern. You can also pass a closure as a second argument to access capture groups and transform the matches.

What happens when I call findAll() without a closure?

Calling findAll() without a closure filters out all falsy values from the collection. In Groovy, null, 0, empty string (”), false, and empty collections are considered falsy. This is a quick way to clean up data by removing unwanted empty or null entries.

Previous in Series: Groovy find() – Find the First Match in a Collection

Next in Series: Groovy Exception Handling – try-catch-finally

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 *