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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 =~.
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.
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.
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.
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.
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
- Pick the Right Filter Type: Use a class for type checks, a range for bounds, a collection for whitelists, and a closure when the test needs more than one comparison.
- Anchor or Wildcard Patterns Deliberately: A regex filter
must match the whole string, so use
.*or the=~operator when you want to find a fragment. - Be Careful With Truthiness:
grep()also removes zero, false, and empty strings, not only nulls. - Use a Set for Large Whitelists: Membership tests on a list
are linear, so convert long collections to a
Setfirst.
Source
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
List all Groovy tutorials.