ZetCode

Groovy List Grep Function

last modified October 15, 2026

The grep method filters a list and returns the elements that match a condition. The condition can be a closure, a class, a regular expression, a range, or a collection, and grep applies the same rule to each of them: it keeps an element when the filter's isCase test accepts it. Because the rule is the same for every filter type, one method covers many filtering tasks that would otherwise need separate loops or conditions. This tutorial explains how grep decides what to keep and shows each filter type with its output.

How grep Decides

grep does not use a plain equality test. For each element it asks the filter object whether it accepts the element, using Groovy's isCase method. Each kind of filter defines that test differently: a class checks the type, a regular expression checks the text, a range or collection checks membership, and a closure returns its result. Knowing these rules explains most of the behavior covered below.

IsCaseRules.groovy
println Number.isCase(5)
println Number.isCase('x')
println((~/ap.*/).isCase('apple'))
println((~/ap/).isCase('apple'))
println((1..5).isCase(3))
println((1..5).isCase(7))
println(['a', 'b'].isCase('c'))
println({ it > 3 }.isCase(5))
println({ it > 3 }.isCase(1))
println('apple'.isCase('apple'))
println('apple'.isCase('x'))

The sample calls isCase directly, so you can see each rule without a list in the way. Each line prints true or false for one kind of filter. Note the regex line that returns false for ap: a pattern must match the whole string unless it includes .*.

$ groovy IsCaseRules.groovy
true
false
true
false
true
false
false
true
false
true
false

Basic Usage of grep

The grep method returns a new list that contains only the elements accepted by the filter. The original list is left untouched, so you can keep both versions.

BasicGrep.groovy
def numbers = [1, 2, 3, 4, 5, 6]
def evenNumbers = numbers.grep { it % 2 == 0 }

println evenNumbers
println numbers

Here a closure is the filter. It receives each element as it, and every element for which the closure returns a truthy value is kept. Printing the original list at the end confirms that nothing was removed from it.

$ groovy BasicGrep.groovy
[2, 4, 6]
[1, 2, 3, 4, 5, 6]

Filtering by Type

When the filter is a class, grep keeps the elements that are instances of that class, including subclasses. This is the simplest way to separate values of different types in one list.

FilterByType.groovy
def mixedList = [1, "Groovy", 3.14, true, "Java"]

println mixedList.grep(String)
println mixedList.grep(Number)

The mixed list below holds strings, a double, a boolean, and an integer. Filtering by String keeps only the strings. Filtering by Number keeps every numeric type, so the 3.14 double and the integer are both returned.

$ groovy FilterByType.groovy
[Groovy, Java]
[1, 3.14]

Filtering by Regular Expression

A regular expression used as the filter is tested with Pattern.isCase, and that test requires the whole string to match the pattern, not just part of it. That is why ~/a/ returns nothing: no word consists of the single letter a.

FilterByRegex.groovy
def words = ["apple", "banana", "cherry", "date"]

println words.grep(~/a/)
println words.grep(~/.*a.*/)
println words.grep { it =~ /a/ }

To find words that merely contain a letter, either wrap the pattern in .* on both sides, or use a closure with the =~ operator, which searches for the pattern anywhere in the string. Both approaches return the same words here.

$ groovy FilterByRegex.groovy
[]
[apple, banana, date]
[apple, banana, date]

Filtering by Range

A range as the filter keeps the elements that fall inside it. Numeric ranges are inclusive at both ends, so 20..40 includes 20 and 40. The same works for character ranges, which is useful for single-letter keys or grades.

FilterByRange.groovy
def numbers = [10, 20, 30, 40, 50]

println numbers.grep(20..40)
println(['b', 'x', 'c', 'a'].grep('a'..'c'))

String ranges only compare strings of the same length, so the character range below uses single letters. Values outside the range, such as x, are dropped.

$ groovy FilterByRange.groovy
[20, 30, 40]
[b, c, a]

Filtering by Collection

A collection as the filter keeps the elements that the collection contains. This reads like a whitelist: the list of allowed values is the filter.

FilterByCollection.groovy
def numbers = [1, 2, 3, 4, 5, 6]
def allowed = [2, 4, 6]

println numbers.grep(allowed)

Internally each element is checked with contains. For a short list that is fine. For large lists, convert the allowed values to a Set first, because membership tests on a set are much faster than on a list.

$ groovy FilterByCollection.groovy
[2, 4, 6]

Filtering by Custom Condition

Closures are the most flexible filter. Whatever condition you can express as a boolean test on one element can be placed in the closure. The closure is called once per element, and the result decides whether that element stays in the output.

CustomCondition.groovy
def numbers = [1, 2, 3, 4, 5, 6]

println numbers.grep { it > 3 }
println numbers.grep { it % 2 == 0 && it > 3 }

The next example combines two conditions with &&. Both must hold for an element to be kept, so the result contains only the even numbers that are also greater than 3.

$ groovy CustomCondition.groovy
[4, 5, 6]
[4, 6]

Removing Empty and Null Values

Calling grep() with no argument uses the element's truth value as the filter. In Groovy, null, false, zero, and empty strings and collections are all falsy, so they are removed. This is a compact way to clean up a list.

RemoveNulls.groovy
def values = [1, null, '', 0, 'x', false]

println values.grep()
println values.grep { it != null }

Be aware that this removes more than nulls. The value 0 and the empty string are removed too. The empty string is kept by the second line, which is why it appears as a blank entry in its output. If you only want to drop null, use an explicit closure as shown in the second line.

$ groovy RemoveNulls.groovy
[1, x]
[1, , 0, x, false]

Filtering by Index

grep passes only the element to its closure, not the index. To filter by position, first pair each element with its index using indexed(), which returns a map from index to value. Filter the entries by their key, then take the values back with the spread operator *.value.

FilterByIndex.groovy
def numbers = [10, 20, 30, 40, 50]

def evenPositions = numbers.indexed().grep { e -> e.key % 2 == 0 }*.value
println evenPositions

The result contains the elements at even positions: 10, 30, and 50.

$ groovy FilterByIndex.groovy
[10, 30, 50]

Filtering Objects by Property

With objects, the closure reads a property of each element. This is the usual way to filter domain objects in a list.

FilterByProperties.groovy
class Person {
    String name
    int age
}

def people = [
    new Person(name: "Alice", age: 30),
    new Person(name: "Bob", age: 25),
    new Person(name: "Charlie", age: 35)
]

def adults = people.grep { it.age >= 30 }
println adults*.name

The spread operator *.name collects the name property from every person in the filtered list. Without it, the output would be a list of Person objects instead of names.

$ groovy FilterByProperties.groovy
[Alice, Charlie]

Case-Insensitive Matching

A regex filter is case-sensitive by default. Adding the inline flag (?i) makes the match ignore case. Because the test must search rather than match the whole string, this example uses a closure with =~.

CaseInsensitiveMatch.groovy
def words = ["Apple", "banana", "Cherry", "Date"]

println words.grep { it =~ /(?i)a/ }

Only words containing an a or A are kept. Cherry has no letter a, so it is excluded.

$ groovy CaseInsensitiveMatch.groovy
[Apple, banana, Date]

Negative Filtering

To keep the elements that do not match a condition, negate the closure with !. The result for !(it % 2 == 0) is the odd numbers.

NegativeFilter.groovy
def numbers = [1, 2, 3, 4, 5, 6]

println numbers.grep { !(it % 2 == 0) }
println numbers.findAll { !(it % 2 == 0) }

findAll with the same closure returns identical results for closures. The difference is in the other filter types: grep accepts classes, ranges, and patterns, which findAll does not, so grep is the more general tool.

$ groovy NegativeFilter.groovy
[1, 3, 5]
[1, 3, 5]

Filtering with a Method Reference

A method reference such as filter.&isEven passes an existing method as the filter. It works with any method that takes one argument and returns a boolean. This keeps the test in a named method, which is easier to reuse and to test than an inline closure.

MethodReferenceFilter.groovy
class Filter {
    boolean isEven(n) { n % 2 == 0 }
}

def numbers = [1, 2, 3, 4, 5, 6]
def filter = new Filter()

println numbers.grep(filter.&isEven)

The method is called once per element. Its return value decides whether the element is kept.

$ groovy MethodReferenceFilter.groovy
[2, 4, 6]

Matching Any of Several Patterns

To keep elements that match at least one of several patterns, keep the patterns in a list and test each element against all of them with any. Inside the closure, =~ searches for each pattern anywhere in the word.

MultiplePatterns.groovy
def words = ["apple", "banana", "cherry", "date", "kiwi", "fig"]
def patterns = [~/a/, ~/e/]

println words.grep { word -> patterns.any { word =~ it } }

Words that contain an a or an e are kept. kiwi and fig contain neither letter and are dropped.

$ groovy MultiplePatterns.groovy
[apple, banana, cherry, date]

grep and findAll

grep and findAll both return the matching elements, and for closure conditions they give the same result. Choose grep when the filter is a class, a range, a collection, or a pattern, because it accepts all of those. Choose findAll when the logic is a closure and you want the code to read as an explicit predicate.

GrepVsFindAll.groovy
def mixed = [1, 'two', 3.0, 'four', 5]

println mixed.grep(Number)
println mixed.findAll { it instanceof Number }

The example filters the same mixed list both ways. The results are identical because the filter is a class check, which findAll expresses with instanceof.

$ groovy GrepVsFindAll.groovy
[1, 3.0, 5]
[1, 3.0, 5]

Best Practices for Using grep

Source

Groovy GDK documentation

In this tutorial, we covered how grep chooses elements through isCase and applied it to classes, regular expressions, ranges, collections, closures, indexes, object properties, and method references. Together these show that grep is a single filtering method whose behavior depends on the filter you give it.

Author

My name is Jan Bodnar, and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all Groovy tutorials.