ZetCode

Groovy Regular Expressions

last modified October 15, 2026

Regular expressions describe patterns in text, and Groovy wraps Java's Pattern and Matcher in a set of so-called slashy and dollar-slashy operators that make matching feel like part of the language. You can test for a match, extract groups, replace text, and split strings without ever calling a constructor. This tutorial covers the operators and the full Matcher API with practical examples.

The Match and Find Operators

Two operators dominate Groovy regex work. The find operator =~ locates a match anywhere in the string and returns a Matcher, while the match operator ==~ requires the entire string to match and returns a boolean.

MatchOperators.groovy
def text = "The year is 2026"

def matcher = text =~ /\d+/
println matcher instanceof java.util.regex.Matcher
println matcher.find()
println matcher.group()

println(text ==~ /.*\d+.*/)
println("2026" ==~ /\d+/)

The pattern is written with slashy syntax /\d+/, which avoids escaping backslashes. =~ yields a matcher whose first match is 2026. ==~ returns true only when the whole string fits the pattern, so it fails for the sentence but succeeds for the bare number.

$ groovy MatchOperators.groovy
true
true
2026
true
true

Creating a Pattern

The pattern operator ~ compiles a Pattern object up front. Reusing a compiled pattern is more efficient when the same expression is applied many times.

PatternOperator.groovy
import java.util.regex.Pattern

def pattern = ~/\bcat\b/

println pattern instanceof Pattern
println pattern.matcher("the cat sat").find()
println pattern.matcher("concatenate").find()

The word-boundary pattern matches cat in the first sentence but not inside concatenate. Compiling once and calling matcher repeatedly keeps the reusable pattern out of the per-call overhead.

$ groovy PatternOperator.groovy
true
true
false

Using Pattern and Matcher

For more control you can work with Pattern.compile and matcher directly. Iterating the matcher visits every occurrence in the input.

FindAll.groovy
import java.util.regex.Pattern

def text = "Emails: alice@example.com and bob@test.org"
def pattern = Pattern.compile(/\w+@\w+\.\w+/)
def matcher = pattern.matcher(text)

while (matcher.find()) {
    println matcher.group()
}

Each call to find advances to the next match, and group() returns the matched text. This is the standard Java idiom, fully available in Groovy when you need explicit control over the search.

$ groovy FindAll.groovy
alice@example.com
bob@test.org

Extracting Matches with findAll

Groovy's findAll collects matches into a list in a single expression, which is often all you need.

FindAllList.groovy
def text = "Dates: 2024-01-15, 2025-06-30, 2026-12-01"

def dates = text.findAll(/\d{4}-\d{2}-\d{2}/)
println dates

def years = text.findAll(/\d{4}/) { it.toInteger() > 2024 }
println years

The first call returns every ISO date, and the second filters matches with a closure, keeping only years after 2024. Combining findAll with a predicate avoids boring loops over the matcher.

$ groovy FindAllList.groovy
[2024-01-15, 2025-06-30, 2026-12-01]
[false, true, true]

Capturing Groups

Parentheses create groups, and numerical indices read them from the matcher. Group zero is the whole match, group one is the first parenthesized part, and so on.

Groups.groovy
def text = "2026-10-15"

def matcher = text =~ /(\d{4})-(\d{2})-(\d{2})/
if (matcher.matches()) {
    println "year:  ${matcher.group(1)}"
    println "month: ${matcher.group(2)}"
    println "day:   ${matcher.group(3)}"
    println matcher.groupCount()
}

The pattern splits the date into year, month, and day. groupCount() returns three, and each numbered group yields its substring. This is the foundation of parsing structured text.

$ groovy Groups.groovy
year:  2026
month: 10
day:   15
3

Named Groups

Named groups with the (?<name>...) syntax make patterns self-documenting, and Groovy exposes them as matcher.group('name').

NamedGroups.groovy
def text = "Alice: 30, Bob: 25"

def pattern = /(?<name>\w+): (?<age>\d+)/
def matcher = text =~ pattern

while (matcher.find()) {
    println "${matcher.group('name')} is ${matcher.group('age')}"
}

Each match exposes the whole text through m[0] and the age through the named group. Named groups reduce off-by-one errors when the pattern has many captures.

$ groovy NamedGroups.groovy
Alice is 30
Bob is 25

Replacing Text

replaceAll and replaceFirst accept a regex pattern and a replacement string. A closure replacement can compute the substitute per match.

Replacement.groovy
def text = "phone: 555-1234, office: 555-5678"

println text.replaceAll(/\d{3}-\d{4}/, "XXX-XXXX")
println text.replaceFirst(/555-(\d{4})/) { m -> "(555) ${m[1]}" }

The first line masks every number, and the second reformats only the first match using a closure that captures the last four digits. Closure replacements keep logic adjacent to the pattern.

$ groovy Replacement.groovy
phone: XXX-XXXX, office: XXX-XXXX
phone: (555) 1234, office: 555-5678

Splitting Strings

The split method on strings accepts a regex, which is handy for separating fields that a single delimiter cannot describe.

Splitting.groovy
def record = "apple,  banana;  cherry"
def parts = record.split(/[,;]\s*/)

parts.each { println it }

The pattern matches a comma or semicolon followed by optional whitespace, so the result is cleanly trimmed. A well-chosen delimiter pattern saves a separate cleaning pass.

$ groovy Splitting.groovy
apple
banana
cherry

Case-Insensitive and Multiline Flags

Inline flags such as (?i) for case insensitivity and (?m) for multiline anchors tune the engine without extra arguments.

Flags.groovy
def text = "Groovy is great, GROOVY is fast"

def matches = text.findAll(/(?i)groovy/)
println matches

println("end" ==~ /(?i)END/)

def lines = "first line\nsecond line"
println lines.findAll(/(?m)^\w+/)

The (?i) flag lets the pattern match both capitalized and lowercase forms, returning two hits. The (?m) flag makes ^ match at the start of every line rather than only at the start of the input, so the last example returns the first word of each line.

$ groovy Flags.groovy
[Groovy, GROOVY]
true
[first, second]

Validating Input

A common use of regex is validating that user input conforms to a known format. The full-match operator ==~ is the right tool because it forbids partial matches.

Validation.groovy
def email = ~/\A[\w.+-]+@[\w-]+\.[\w.]+\z/

def valid = ["alice@example.com", "bad@@mail", "bob@test.org"]

valid.each { address ->
    println "$address -> ${email.matcher(address).matches()}"
}

The anchored pattern requires the whole string to be a well-formed address, so only the first and third entries pass. Anchors prevent a partial match from slipping through validation.

$ groovy Validation.groovy
alice@example.com -> true
bad@@mail -> false
bob@test.org -> true

Best Practices for Regular Expressions

Source

Groovy Regular Expression Operators

In this tutorial, we covered Groovy regular expressions through the =~, ==~, and ~ operators, the Pattern and Matcher API, capturing and named groups, replacement, splitting, inline flags, and validation. Groovy's operators turn common regex tasks into concise, readable expressions.

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.