15 Essential Groovy Closure Examples – The Complete Guide

Groovy closures are one of the language’s most useful features. See 15 practical examples covering syntax, parameters, delegate, composition, and real-world patterns. Tested on Groovy 5.x.

“Closures are the Swiss Army knife of Groovy. Once you understand them, every other feature clicks into place.”

Venkat Subramaniam, Programming Groovy 2

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

If there’s one feature that defines Groovy’s personality, it’s closures. They show up everywhere — in loops, in builders, in DSLs, in collection processing, in event handlers. You can’t write idiomatic Groovy without them. And once you truly understand closures, everything else in the language makes a whole lot more sense.

A closure in Groovy is an open, anonymous block of code that can take arguments, return a value, and be assigned to a variable. Think of it as a portable chunk of logic you can pass around like data. If you’ve used lambdas in Java 8+ or blocks in Ruby, you’re already halfway there — but Groovy closures are more powerful than both.

In this post, we’ll cover Groovy closures from the ground up with 15 tested examples. We’ll start with the basics — what a closure is, how to declare one, and what the implicit it parameter does. Then we’ll move into real-world patterns like using closures with each(), collect(), and findAll(). We’ll also cover closure composition, coercion to interfaces, the owner/delegate/this trinity, and how closures power DSLs and builders. We’ll also compare closures to methods and Java lambdas so you know when each one fits best.

If you’re coming from the previous post on Groovy Sleep and Timing, welcome back. This one is a big topic, so grab your coffee and let’s dig in.

What Is a Closure in Groovy?

A closure in Groovy is an instance of groovy.lang.Closure. According to the official Groovy documentation, a closure is “an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable.” That’s a mouthful, so let’s break it down:

  • Open: A closure can access variables from its surrounding scope (its “enclosing environment”). This is what makes it a closure — it “closes over” variables from the outside.
  • Anonymous: Unlike a method, a closure doesn’t need a name (though you can assign it to a variable to reference it later).
  • Block of code: It’s a chunk of executable statements wrapped in curly braces { }.
  • Takes arguments: Closures can accept zero or more parameters, declared before the -> arrow.
  • Returns a value: Every closure returns the value of its last expression (or an explicit return).
  • Assignable to a variable: You can store a closure in a variable and call it later, pass it to methods, or return it from other closures.

The simplest closure looks like this: { println "Hello" }. That’s it — curly braces around some code. But don’t let the simplicity fool you. Closures are one of the most useful features in Groovy.

Why Use Closures?

You might be wondering: “If I already have methods, why do I need closures?” Fair question. Here’s what closures give you that plain methods don’t:

  • First-class citizens: Closures can be stored in variables, passed as arguments, and returned from functions. Methods can’t do that directly in Groovy (you need .& to get a method reference).
  • Capture surrounding state: Closures remember the variables from where they were created. This makes them perfect for callbacks, event handlers, and deferred execution.
  • Concise syntax: Instead of defining a whole class or method, you can inline behavior right where it’s needed. Compare list.each { println it } to writing a for-loop.
  • DSL building blocks: Closures with delegate resolution are the foundation of Groovy’s builder pattern and DSLs (like Gradle build scripts).
  • Functional programming: Closures enable map/filter/reduce patterns, composition, currying, and memoization out of the box.

When to Use Closures

Use closures when you need to:

  • Iterate over collections (each, eachWithIndex)
  • Transform data (collect, findAll, inject)
  • Define callbacks or event handlers
  • Build DSLs or configuration blocks (think Gradle, Grails)
  • Implement strategy patterns without full-blown classes
  • Create reusable logic that you want to pass around
  • Do anything that feels like “pass this behavior as a parameter”

Avoid closures when a simple method call or a plain loop is clearer. Not everything needs to be functional. If you’re just calling a method once and don’t need to pass it around, a regular method definition is perfectly fine.

How Closures Work Under the Hood

When you write def greet = { name -> "Hello, $name" }, Groovy compiles that into a class that extends groovy.lang.Closure. The closure object holds references to three important things:

  • this: The enclosing class where the closure is defined. Always points to the class instance, even for nested closures.
  • owner: The enclosing object, which could be the class instance or another closure (if nested).
  • delegate: An object that the closure can use to resolve method calls and property references. By default, this equals owner, but it can be changed — and changing it is how Groovy builders and DSLs work.

The relationship between these three properties is what makes Groovy closures uniquely powerful. We’ll explore owner, delegate, and this in detail in the next post, but you’ll see them in action here too.

Closure Syntax

Here are the different ways to write a closure in Groovy:

Closure Syntax Variations

// 1. No parameters (zero-arg closure)
def sayHello = { println "Hello!" }

// 2. Implicit 'it' parameter (single argument)
def greet = { println "Hello, $it!" }

// 3. Explicit single parameter
def greetExplicit = { String name -> println "Hello, $name!" }

// 4. Multiple parameters
def add = { int a, int b -> a + b }

// 5. Default parameter values
def power = { base, exp = 2 -> base ** exp }

// 6. Variable arguments (varargs)
def sum = { int... nums -> nums.sum() }

// 7. No arrow, no params -- just code
def getCurrentTime = { new Date().toString() }

The -> arrow separates the parameter list from the closure body. If you have no parameters and don’t use it, you can skip the arrow entirely. If you have exactly one parameter, you can skip declaring it and use the implicit it variable instead.

To call a closure, you use either closure.call(args) or the shorthand closure(args). Both do exactly the same thing.

15 Practical Closure Examples

Example 1: Your First Closure

What we’re doing: Creating a basic closure, assigning it to a variable, and calling it two different ways.

Example 1: Basic Closure

// Define a closure and assign it to a variable
def greet = { println "Hello from a closure!" }

// Call it using parentheses
greet()

// Call it using .call()
greet.call()

// Check what type it is
println "Type: ${greet.getClass().name}"
println "Is Closure? ${greet instanceof Closure}"

Output

Hello from a closure!
Hello from a closure!
Type: Script1$_run_closure1
Is Closure? true

What happened here: We defined a closure with no parameters and assigned it to the variable greet. Both greet() and greet.call() invoke it. The class name Script1$_run_closure1 tells us Groovy compiled it into an inner class that extends Closure. Every closure you write becomes an object — that’s what makes them first-class citizens.

Example 2: The Implicit ‘it’ Parameter

What we’re doing: Using the implicit it parameter that Groovy provides when a closure has a single argument.

Example 2: Implicit ‘it’ Parameter

// 'it' is the default parameter name when you don't declare one
def shout = { println it.toUpperCase() }

shout("hello groovy")
shout("closures are awesome")

// You can also name it explicitly -- same result
def shoutExplicit = { text -> println text.toUpperCase() }
shoutExplicit("explicit parameter")

// 'it' is null when no argument is passed to a single-param closure
def check = { println "it = ${it}" }
check()
check("something")

Output

HELLO GROOVY
CLOSURES ARE AWESOME
EXPLICIT PARAMETER
it = null
it = something

What happened here: When a closure doesn’t declare any parameters with the -> arrow, Groovy automatically provides an implicit parameter called it. This is one of Groovy’s most recognizable features. If you call the closure without passing an argument, it defaults to null. For readability, many developers prefer naming the parameter explicitly when the closure body is more than a single line.

Example 3: Closures with Multiple Parameters

What we’re doing: Defining closures that accept two or more parameters with explicit types.

Example 3: Multiple Parameters

// Two parameters
def add = { int a, int b -> a + b }
println "3 + 7 = ${add(3, 7)}"

// Three parameters with mixed types
def describe = { String name, int age, String city ->
    "$name is $age years old and lives in $city"
}
println describe("Alice", 30, "New York")

// Default parameter values
def greet = { String name, String greeting = "Hello" ->
    "$greeting, $name!"
}
println greet("Bob")
println greet("Bob", "Hey")

// Untyped parameters (dynamic)
def multiply = { a, b -> a * b }
println "5 * 3 = ${multiply(5, 3)}"
println "Repeat: ${multiply('Go', 3)}"

Output

3 + 7 = 10
Alice is 30 years old and lives in New York
Hello, Bob!
Hey, Bob!
5 * 3 = 15
Repeat: GoGoGo

What happened here: With multiple parameters, you must declare them explicitly before the -> arrow — the implicit it only works for a single parameter. You can type parameters or leave them untyped (dynamic). Default values work just like they do in regular Groovy methods. Notice how multiply works with both integers and strings because of Groovy’s operator overloading — * on a string repeats it.

Example 4: Closure as the Last Argument

What we’re doing: Demonstrating Groovy’s trailing closure syntax, where a closure passed as the last argument can be placed outside the parentheses.

Example 4: Trailing Closure Syntax

// A method that takes a closure as its last parameter
def repeat(int times, Closure action) {
    times.times { action(it) }
}

// Standard call -- closure inside parentheses
repeat(3, { println "Iteration $it" })

println "---"

// Trailing closure syntax -- closure OUTSIDE parentheses
repeat(3) { println "Iteration $it (trailing)" }

println "---"

// When the closure is the ONLY argument, you can skip parentheses entirely
def names = ["Alice", "Bob", "Charlie"]
names.each { println it }

println "---"

// Multiple examples of trailing closures you use every day
[1, 2, 3, 4, 5].findAll { it % 2 == 0 }.each { println "Even: $it" }

Output

Iteration 0
Iteration 1
Iteration 2
---
Iteration 0 (trailing)
Iteration 1 (trailing)
Iteration 2 (trailing)
---
Alice
Bob
Charlie
---
Even: 2
Even: 4

What happened here: This trailing closure syntax is one of the things that makes Groovy code so readable. When a method’s last parameter is a closure, you can move it outside the parentheses. And if it’s the only argument, you can drop the parentheses altogether. That’s why names.each { println it } looks so clean — the closure is the only argument to each().

Example 5: Closure Return Values

What we’re doing: Understanding how closures return values — both implicitly and explicitly.

Example 5: Return Values

// Implicit return -- last expression is the return value
def square = { it * it }
println "5 squared = ${square(5)}"

// Explicit return
def absolute = { int n ->
    if (n < 0) return -n
    return n
}
println "absolute(-7) = ${absolute(-7)}"
println "absolute(3) = ${absolute(3)}"

// IMPORTANT: 'return' in a closure only exits the closure, NOT the enclosing method
def findFirst = { List items ->
    for (item in items) {
        if (item > 3) return item  // exits the closure, not the outer method
    }
    return null
}
println "First > 3: ${findFirst([1, 2, 5, 8])}"

// Multi-line closure -- last expression wins
def analyze = { String text ->
    def words = text.split(' ')
    def count = words.size()
    "Word count: $count"  // this is the return value
}
println analyze("Groovy closures are powerful")

Output

5 squared = 25
absolute(-7) = 7
absolute(3) = 3
First > 3: 5
Word count: 4

What happened here: By default, a closure returns the value of its last expression. You don’t need to write return unless you want to exit early. One critical thing to understand: return inside a closure only exits the closure itself — it does NOT return from the enclosing method. This is different from how return works inside a for-loop in a method. We’ll revisit this distinction in the pitfalls section.

Example 6: Closures Capture Surrounding Variables

What we’re doing: Showing how closures “close over” variables from their enclosing scope.

Example 6: Variable Capture (Closing Over)

// Closure captures the 'greeting' variable from the outer scope
def greeting = "Hello"
def greet = { name -> "$greeting, $name!" }

println greet("Alice")

// Change the outer variable -- the closure sees the change!
greeting = "Hola"
println greet("Bob")

// Counter example -- closure modifies outer variable
def counter = 0
def increment = { counter++ }

increment()
increment()
increment()
println "Counter: $counter"

// Closures in a loop -- each captures the same variable
def closures = []
for (i in 0..2) {
    closures << { println "i = $i" }
}
closures.each { it() }

Output

Hello, Alice!
Hola, Bob!
Counter: 3
i = 2
i = 2
i = 2

What happened here: This is the “closure” in “closure” — the block of code closes over variables from its surrounding scope. When we changed greeting from “Hello” to “Hola”, the closure saw the new value because it holds a reference to the variable, not a copy. The counter example shows that closures can also modify captured variables. In the loop, all three closures capture the same variable i by reference. By the time we call them, the loop has finished and i holds its final value of 2, so all three closures print i = 2. This is a common “gotcha” with closures — they capture the variable itself, not its value at the moment the closure was created.

Example 7: Using Closures with each(), collect(), and findAll()

What we’re doing: Using closures with Groovy’s most common collection methods.

Example 7: Closures with Collections

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

// each() -- iterate and do something (returns void)
print "Numbers: "
numbers.each { print "$it " }
println()

// collect() -- transform each element (returns new list)
def doubled = numbers.collect { it * 2 }
println "Doubled: $doubled"

// findAll() -- filter elements (returns matching items)
def evens = numbers.findAll { it % 2 == 0 }
println "Evens: $evens"

// find() -- return first match
def firstBig = numbers.find { it > 5 }
println "First > 5: $firstBig"

// any() and every() -- boolean checks
println "Any > 8? ${numbers.any { it > 8 }}"
println "All > 0? ${numbers.every { it > 0 }}"

// Chaining closures together
def result = numbers
    .findAll { it % 2 != 0 }
    .collect { it * it }
    .findAll { it > 10 }
println "Odd squares > 10: $result"

Output

Numbers: 1 2 3 4 5 6 7 8 9 10
Doubled: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Evens: [2, 4, 6, 8, 10]
First > 5: 6
Any > 8? true
All > 0? true
Odd squares > 10: [25, 49, 81]

What happened here: This is where closures truly shine in Groovy. Every one of these collection methods — each, collect, findAll, find, any, every — takes a closure as its argument. The trailing closure syntax makes the code read almost like natural language. The chained example at the bottom shows the power of functional-style programming: find odd numbers, square them, keep the ones greater than 10. For more on each(), check out the Groovy Each Loop post. We’ll dive deeper into collect, inject, and groupBy in the upcoming Higher-Order Functions post.

Example 8: Storing Closures in Variables and Maps

What we’re doing: Treating closures as data — storing them in variables, lists, and maps for dynamic dispatch.

Example 8: Closures in Variables and Maps

// Store closures in a map -- poor man's strategy pattern
def operations = [
    add:      { a, b -> a + b },
    subtract: { a, b -> a - b },
    multiply: { a, b -> a * b },
    divide:   { a, b -> b != 0 ? a / b : "Division by zero" }
]

// Dynamic dispatch based on string key
["add", "subtract", "multiply", "divide"].each { op ->
    println "$op(10, 3) = ${operations[op](10, 3)}"
}

println "---"

// Store closures in a list
def pipeline = [
    { it.trim() },
    { it.toLowerCase() },
    { it.replaceAll(/\s+/, '-') }
]

// Process through the pipeline
def input = "  Hello World  "
def result = input
pipeline.each { transform -> result = transform(result) }
println "Pipeline result: '$result'"

Output

add(10, 3) = 13
subtract(10, 3) = 7
multiply(10, 3) = 30
divide(10, 3) = 3.3333333333
---
Pipeline result: 'hello-world'

What happened here: Because closures are objects, you can store them anywhere — in variables, lists, maps, or even pass them over a network (with serialization). The map-based approach is a clean alternative to switch statements or if-else chains. The pipeline example shows how you can compose transformations by storing closures in a list and running data through each one sequentially. This pattern is incredibly useful for data processing, ETL pipelines, and string sanitization.

Example 9: Closure Coercion to Interfaces (SAM Types)

What we’re doing: Showing how Groovy can automatically convert a closure to a single-abstract-method (SAM) interface.

Example 9: Closure Coercion to SAM Interfaces

// Using a closure where a Runnable is expected
Thread t1 = new Thread({ println "Running in thread: ${Thread.currentThread().name}" } as Runnable)
t1.start()
t1.join()

// Groovy can also do it automatically (SAM coercion)
Thread t2 = new Thread({ println "Auto-coerced: ${Thread.currentThread().name}" })
t2.start()
t2.join()

// Works with Comparator too
def names = ["Charlie", "Alice", "Bob"]
def sorted = names.sort { a, b -> a.length() <=> b.length() }
println "Sorted by length: $sorted"

// Using 'as' keyword for explicit coercion
Comparator comp = { a, b -> a <=> b } as Comparator
println "Comparator sorted: ${["Zebra", "Apple", "Mango"].sort(comp)}"

// Custom SAM interface
interface Transformer {
    String transform(String input)
}

Transformer upper = { it.toUpperCase() }
println "Transformed: ${upper.transform("groovy")}"

Output

Running in thread: Thread-0
Auto-coerced: Thread-1
Sorted by length: [Bob, Alice, Charlie]
Comparator sorted: [Apple, Mango, Zebra]
Transformed: GROOVY

What happened here: Groovy can automatically convert a closure into any interface that has exactly one abstract method (a SAM type). This is similar to Java 8 lambdas but more flexible. You can use the as keyword for explicit coercion, or let Groovy figure it out automatically when the target type is known. This means you can pass closures to Java APIs that expect Runnable, Callable, Comparator, or any custom functional interface.

Example 10: Owner, Delegate, and This

What we’re doing: Exploring the three scope references every closure carries.

Example 10: Owner, Delegate, and This

class MyClass {
    String name = "MyClass"

    def outerClosure() {
        def outer = {
            println "this class:  ${this.class.name}"
            println "owner class: ${owner.class.name}"
            println "delegate:    ${delegate.class.name}"

            // Nested closure
            def inner = {
                println "--- inner ---"
                println "this class:  ${this.class.name}"
                println "owner class: ${owner.class.name}"
                println "delegate:    ${delegate.class.name}"
            }
            inner()
        }
        outer()
    }
}

new MyClass().outerClosure()

Output

this class:  MyClass
owner class: MyClass
delegate:    MyClass
--- inner ---
this class:  MyClass
owner class: MyClass$_outerClosure_closure1
delegate:    MyClass$_outerClosure_closure1

What happened here: For the outer closure, this, owner, and delegate all point to the enclosing MyClass instance. But for the nested inner closure, this still points to MyClass (it always points to the enclosing class), while owner and delegate point to the outer closure. This distinction matters hugely when building DSLs. We’ll explore delegate strategies in depth in the next post about closure parameters and delegate.

Example 11: Closure Composition (<< and >>)

What we’re doing: Composing closures together using the left-shift (<<) and right-shift (>>) operators.

Example 11: Closure Composition

def trim = { it.trim() }
def lower = { it.toLowerCase() }
def exclaim = { it + "!" }

// >> (right shift) means "then" -- apply left first, then right
def cleanAndShout = trim >> lower >> exclaim
println cleanAndShout("  HELLO GROOVY  ")

// << (left shift) means "compose" -- apply right first, then left
def shoutAndClean = exclaim << lower << trim
println shoutAndClean("  HELLO GROOVY  ")

// Composition with numeric closures
def double_ = { it * 2 }
def addThree = { it + 3 }

def doubleFirst = double_ >> addThree   // double, then add 3
def addFirst = addThree >> double_       // add 3, then double

println "double then add3: 5 -> ${doubleFirst(5)}"    // (5*2)+3 = 13
println "add3 then double: 5 -> ${addFirst(5)}"       // (5+3)*2 = 16

// Building a text processing pipeline with composition
def slugify = { String s -> s.trim() } >>
              { it.toLowerCase() } >>
              { it.replaceAll(/[^a-z0-9\s]/, '') } >>
              { it.replaceAll(/\s+/, '-') }

println "Slug: ${slugify("  Hello, World! 2026  ")}"

Output

hello groovy!
hello groovy!
double then add3: 5 -> 13
add3 then double: 5 -> 16
Slug: hello-world-2026

What happened here: The >> operator creates a new closure that applies the left closure first, then passes its result to the right closure. The << operator goes the other direction — right first, then left. This is functional composition, and it’s incredibly powerful for building data processing pipelines. The slugify example chains four transformations into a single reusable closure. No intermediate variables, no nesting — just clean, readable flow.

Example 12: Closures vs Method References

What we’re doing: Comparing closures with method references using the .& operator.

Example 12: Closures vs Method References

// A regular method
def square(int n) { n * n }

// Get a method reference -- this creates a closure!
def squareRef = this.&square
println "Method ref: ${squareRef(5)}"
println "Is Closure? ${squareRef instanceof Closure}"

// Use method reference wherever a closure is expected
def numbers = [1, 2, 3, 4, 5]
println "Squares: ${numbers.collect(this.&square)}"

// String methods as closures
def words = ["hello", "world", "groovy"]
println "Upper: ${words.collect(String.&toUpperCase)}"

// Practical: using method references for readability
def isEven(int n) { n % 2 == 0 }
println "Evens: ${numbers.findAll(this.&isEven)}"

// Closure equivalent -- equally valid
println "Evens: ${numbers.findAll { it % 2 == 0 }}"

Output

Method ref: 25
Is Closure? true
Squares: [1, 4, 9, 16, 25]
Upper: [HELLO, WORLD, GROOVY]
Evens: [2, 4]
Evens: [2, 4]

What happened here: The .& operator converts any method into a closure (a MethodClosure to be precise). This bridge between methods and closures means you can use existing methods anywhere a closure is expected. It’s particularly useful when you already have well-named utility methods and want to pass them to collect, findAll, or sort without wrapping them in a closure.

Example 13: Closure Scope — What Can a Closure Access?

What we’re doing: Demonstrating what variables and methods a closure can see from its enclosing context.

Example 13: Closure Scope

class ScopeDemo {
    String instanceVar = "instance"
    static String staticVar = "static"

    def demonstrate() {
        def localVar = "local"

        def closure = {
            // Can access instance variables
            println "Instance var: $instanceVar"

            // Can access static variables
            println "Static var: $staticVar"

            // Can access local variables from enclosing method
            println "Local var: $localVar"

            // Can access closure's own local variables
            def closureVar = "closure-local"
            println "Closure var: $closureVar"

            // Can call instance methods
            println "Method call: ${helperMethod()}"
        }

        closure()

        // Local variable modified inside closure is visible outside
        def counter = 0
        def inc = { counter++ }
        inc()
        inc()
        println "Counter after closure calls: $counter"
    }

    String helperMethod() { "helper result" }
}

new ScopeDemo().demonstrate()

Output

Instance var: instance
Static var: static
Local var: local
Closure var: closure-local
Method call: helper result
Counter after closure calls: 2

What happened here: A closure has access to everything in its surrounding scope — instance variables, static variables, local variables from the enclosing method, and the class’s methods. This is what “closing over” means. The closure captures references to these variables, not copies. So when the counter is modified inside the closure, the change is visible outside. This makes closures incredibly powerful but also means you need to be careful about unintended side effects on shared mutable state.

Example 14: Real-World Pattern — DSL Builder

What we’re doing: Building a simple DSL (domain-specific language) using closures and delegate — the pattern used by Gradle, Grails, and many Groovy frameworks.

Example 14: DSL Builder Pattern

// A simple HTML builder using closures
class HtmlBuilder {
    StringBuilder sb = new StringBuilder()
    int indent = 0

    def html(Closure content) {
        sb.append("${'  ' * indent}<html>\n")
        indent++
        content.delegate = this
        content.resolveStrategy = Closure.DELEGATE_FIRST
        content()
        indent--
        sb.append("${'  ' * indent}</html>\n")
    }

    def head(Closure content) {
        sb.append("${'  ' * indent}<head>\n")
        indent++
        content.delegate = this
        content()
        indent--
        sb.append("${'  ' * indent}</head>\n")
    }

    def body(Closure content) {
        sb.append("${'  ' * indent}<body>\n")
        indent++
        content.delegate = this
        content()
        indent--
        sb.append("${'  ' * indent}</body>\n")
    }

    def title(String text) {
        sb.append("${'  ' * indent}<title>$text</title>\n")
    }

    def p(String text) {
        sb.append("${'  ' * indent}<p>$text</p>\n")
    }

    String toString() { sb.toString() }
}

// Use the DSL
def builder = new HtmlBuilder()
builder.html {
    head {
        title "My Page"
    }
    body {
        p "Hello from Groovy DSL!"
        p "Closures make this possible."
    }
}

println builder.toString()

Output

<html>
  <head>
    <title>My Page</title>
  </head>
  <body>
    <p>Hello from Groovy DSL!</p>
    <p>Closures make this possible.</p>
  </body>
</html>

What happened here: This is where closures go from “nice syntax” to “game-changing feature.” By setting a closure’s delegate and using DELEGATE_FIRST resolution, we make method calls inside the closure resolve against our builder object instead of the surrounding class. So title "My Page" inside the head { } block calls HtmlBuilder.title(). This is exactly how Gradle build scripts work — dependencies { }, repositories { }, and all those blocks are just closures with delegates.

Example 15: Event Handlers and Callbacks

What we’re doing: Using closures as callbacks and event handlers — a pattern you’ll see in UI frameworks, async programming, and observer patterns.

Example 15: Event Handlers and Callbacks

// Simple event system using closures
class EventEmitter {
    Map<String, List<Closure>> listeners = [:].withDefault { [] }

    void on(String event, Closure handler) {
        listeners[event] << handler
    }

    void emit(String event, Object... args) {
        listeners[event].each { handler ->
            handler(*args)
        }
    }
}

def emitter = new EventEmitter()

// Register event handlers (closures!)
emitter.on("login") { user ->
    println "Welcome back, $user!"
}

emitter.on("login") { user ->
    println "Logging: $user logged in at ${new Date().format('HH:mm:ss')}"
}

emitter.on("error") { code, msg ->
    println "ERROR [$code]: $msg"
}

// Fire events
emitter.emit("login", "Alice")
println "---"
emitter.emit("error", 404, "Page not found")

println "---"

// Callback pattern
def fetchData(Closure onSuccess, Closure onError) {
    try {
        def data = [name: "Groovy", version: "5.0"]
        onSuccess(data)
    } catch (Exception e) {
        onError(e.message)
    }
}

fetchData(
    { data -> println "Success: $data" },
    { error -> println "Failed: $error" }
)

Output

Welcome back, Alice!
Logging: Alice logged in at 16:18:53
---
ERROR [404]: Page not found
---
Success: [name:Groovy, version:5.0]

What happened here: Closures are natural event handlers. The EventEmitter stores closures in a map keyed by event name. When an event fires, it calls all registered closures with the provided arguments. The spread operator (*args) unpacks the arguments array into individual parameters. The callback pattern at the bottom shows another common use: passing success and error handlers as closures. This approach is cleaner than implementing interfaces or abstract classes.

Closures vs Methods vs Lambdas

If you’re coming from Java, you might wonder how Groovy closures compare to Java 8 lambdas and regular methods. Here’s a side-by-side comparison:

FeatureGroovy ClosureJava LambdaGroovy Method
First-class objectYesNo (interface instance)No (use .& for reference)
Can capture mutable varsYesNo (effectively final only)N/A
Has delegateYesNoNo
Can be composed (>> <<)YesandThen/composeNo
Implicit parameter (it)YesNoNo
Can coerce to SAMYesYes (native)No
return behaviorExits closure onlyExits lambda onlyExits method
NamedNo (anonymous)No (anonymous)Yes
Currying supportBuilt-inNo (manual)No
Owner/this/delegateYes (3 references)Captures thisHas this only

Bottom line: Groovy closures are more powerful than Java lambdas. They can capture and modify mutable variables, have a delegate mechanism for DSLs, support currying and composition natively, and carry the implicit it parameter. Java lambdas are lighter-weight but more restricted. If you’re writing Groovy, always use closures — don’t try to mimic Java lambda patterns.

Closures vs Methods vs Lambdas

// Groovy closure -- captures mutable variable
def counter = 0
def inc = { counter++ }
inc()
inc()
println "Closure captured mutable var: $counter"  // 2

// Method reference -- converting method to closure
def double_(int n) { n * 2 }
def doubleRef = this.&double_
println "Method ref: ${doubleRef(5)}"              // 10

// Method reference used with collect
println [1, 2, 3].collect(this.&double_)           // [2, 4, 6]

// Closure with currying (not possible with Java lambdas)
def multiply = { a, b -> a * b }
def triple = multiply.curry(3)
println "Curried triple(5): ${triple(5)}"          // 15

Output

Closure captured mutable var: 2
Method ref: 10
[2, 4, 6]
Curried triple(5): 15

Advanced Closure Patterns

Currying — Partial Application

Currying creates a new closure by fixing one or more parameters of an existing closure. Groovy supports left currying (curry), right currying (rcurry), and index-based currying (ncurry).

Currying

def log = { level, message -> println "[$level] $message" }

// Left curry -- fix the first parameter
def info = log.curry("INFO")
def error = log.curry("ERROR")

info("Application started")
error("Something went wrong")

// Right curry -- fix the last parameter
def greet = { greeting, name -> "$greeting, $name!" }
def helloTo = greet.rcurry("World")
println helloTo("Hello")
println helloTo("Hola")

// Index-based curry -- fix parameter at specific index
def between = { min, value, max -> value >= min && value <= max }
def isPercent = between.ncurry(0, 0).ncurry(1, 100)  // fix min=0, max=100
println "50 is percent? ${isPercent(50)}"
println "150 is percent? ${isPercent(150)}"

Output

[INFO] Application started
[ERROR] Something went wrong
Hello, World!
Hola, World!
50 is percent? true
150 is percent? false

Memoization — Caching Results

Groovy closures have built-in memoize() support that caches return values based on input parameters.

Memoization

// Expensive computation (simulated)
def fibonacci
fibonacci = { int n ->
    if (n <= 1) return n
    fibonacci(n - 1) + fibonacci(n - 2)
}.memoize()

// Without memoize, fib(40) would take seconds
def start = System.currentTimeMillis()
println "fib(30) = ${fibonacci(30)}"
println "Time: ${System.currentTimeMillis() - start} ms"

// Second call is instant -- cached!
start = System.currentTimeMillis()
println "fib(30) = ${fibonacci(30)}"
println "Time (cached): ${System.currentTimeMillis() - start} ms"

// memoizeAtMost(n) -- cache only the last n results
def expensiveOp = { int x ->
    Thread.sleep(10)
    x * x
}.memoizeAtMost(5)

println "Cached: ${expensiveOp(4)}"

Output

fib(30) = 832040
Time: 18 ms
fib(30) = 832040
Time (cached): 0 ms
Cached: 16

Trampoline — Avoiding Stack Overflow in Recursion

For deep recursion, Groovy closures provide trampoline() to convert recursive calls into iterative ones, preventing StackOverflowError.

Trampoline

// Without trampoline, this would overflow for large n
def factorial
factorial = { int n, BigInteger acc = 1 ->
    if (n <= 1) return acc
    factorial.trampoline(n - 1, n * acc)
}.trampoline()

println "10! = ${factorial(10)}"
println "20! = ${factorial(20)}"
println "100! = ${factorial(100)}"

Output

10! = 3628800
20! = 2432902008176640000
100! = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Edge Cases and Pitfalls

Common Pitfalls

DON’T:

  • Confuse return in closures vs methods: return inside a closure exits only the closure, not the enclosing method. If you use each with a closure and put return inside, it acts like continue, not break.
  • Forget that closures capture references, not values: If a captured variable changes after the closure is created, the closure sees the new value.
  • Use { } where Groovy expects a Map: An empty [:] is a map; an empty { } is a closure. Mixing them up causes confusing errors.
  • Overuse closures for simple logic: A plain for-loop or method is sometimes clearer than a chain of closures. Don’t be “clever” at the expense of readability.

DO:

  • Use explicit parameter names instead of it when the closure is more than one line
  • Use { -> } (arrow with no params) to explicitly declare a zero-arg closure and suppress the implicit it
  • Use @ClosureParams and @DelegatesTo annotations for IDE support and static type checking
  • Set resolveStrategy explicitly when changing a closure’s delegate

The ‘return’ Gotcha

The return Gotcha

// GOTCHA: return in each() doesn't break out of the loop
def findFirstEven(List nums) {
    def result = null
    nums.each {
        if (it % 2 == 0) {
            result = it
            return  // This only exits the closure, NOT findFirstEven()!
        }
    }
    return result  // Returns the LAST even, not the first!
}

println "Using each: ${findFirstEven([1, 3, 4, 6, 8])}"  // Returns 8, not 4!

// CORRECT approach: use find() instead
def firstEven = [1, 3, 4, 6, 8].find { it % 2 == 0 }
println "Using find: $firstEven"  // Returns 4

Output

Using each: 8
Using find: 4

This is the single most common closure pitfall in Groovy. The return inside each‘s closure exits that single invocation of the closure, then each moves to the next element. It does NOT break out of the loop. If you need early termination, use find, any, every, or a traditional for-loop.

Zero-Arg Closures vs Implicit ‘it’

Zero-Arg Closure

// This closure has an implicit 'it' parameter
def withIt = { println "it = $it" }
withIt("hello")
println "Param count: ${withIt.maximumNumberOfParameters}"

// This closure explicitly takes ZERO parameters
def noParams = { -> println "No params" }
println "Param count: ${noParams.maximumNumberOfParameters}"

// Calling noParams with an argument throws an error
try {
    noParams("oops")
} catch (MissingMethodException e) {
    println "Error: Cannot pass args to zero-arg closure"
}

Output

it = hello
Param count: 1
No params
Param count: 0
Error: Cannot pass args to zero-arg closure

If you want a closure that truly takes no arguments, use { -> } with the arrow but no parameters. Without the arrow, the closure has an implicit it parameter and accepts one argument.

Performance Considerations

Let’s talk about the elephant in the room: are closures slow? The short answer is “not meaningfully.” The longer answer:

  • Closure creation: Each closure creates an object (an instance of a Closure subclass). This has a small overhead, but modern JVMs are excellent at allocating and garbage-collecting short-lived objects.
  • Method dispatch: Calling a closure goes through Groovy’s dynamic method dispatch, which is slower than a direct Java method call. But the JIT compiler optimizes hot paths, and Groovy’s invoke dynamic support (since Groovy 2.0) significantly reduces this overhead.
  • Memory: Closures hold references to their enclosing scope. For most use cases this is fine, but be cautious in long-lived closures that capture large objects — they can prevent garbage collection.
  • each() vs for-loop: A traditional for-loop is marginally faster than each { } because there’s no closure creation or dynamic dispatch. But for typical application code, the difference is invisible. Optimize readability first.

Performance: for-loop vs each()

def list = (1..10000).toList()
def iterations = 1000

// Warm up
5.times {
    def sum1 = 0; list.each { sum1 += it }
    def sum2 = 0; for (n in list) { sum2 += n }
}

// Benchmark each()
def start = System.nanoTime()
iterations.times {
    def sum = 0
    list.each { sum += it }
}
def eachTime = (System.nanoTime() - start) / 1_000_000

// Benchmark for-loop
start = System.nanoTime()
iterations.times {
    def sum = 0
    for (n in list) { sum += n }
}
def forTime = (System.nanoTime() - start) / 1_000_000

println "each() time:    ${eachTime} ms"
println "for-loop time:  ${forTime} ms"
println "Choose readability over micro-optimization"

Output

each() time:    312 ms
for-loop time:  198 ms
Choose readability over micro-optimization

For 99% of real-world code, the performance difference between closures and direct method calls or loops is irrelevant. Write idiomatic Groovy with closures. Profile your application if you have actual performance problems. Don’t pre-optimize.

Conclusion

Groovy closures are the feature that makes Groovy feel like Groovy. They’re not just anonymous functions — they’re first-class objects with delegate resolution, composition operators, currying, memoization, and trampoline support. They power the collection methods you use every day (each, collect, findAll), they’re the backbone of every Groovy DSL (Gradle, Grails, Spock), and they make callback-driven code clean and readable.

We covered a lot of ground in this post — from basic syntax and the implicit it parameter, through variable capture and collection processing, all the way to composition, DSL builders, and event handling. If this feels like a lot, that’s because closures really are the heart of the language.

In the next post, we’ll go deeper into closure parameters, delegate strategies, and the owner/delegate/this trinity. And after that, we’ll explore higher-order functions like collect, inject, and groupBy that use closures heavily.

Summary

  • A closure is an anonymous block of code defined with { } that can take arguments, return values, and be assigned to variables
  • Single-parameter closures get an implicit it parameter; use { -> } for true zero-arg closures
  • Closures capture variables from their enclosing scope by reference, not by value
  • return inside a closure exits only the closure, not the enclosing method
  • The >> and << operators compose closures into pipelines
  • Closures can be coerced to SAM interfaces, used as callbacks, stored in maps, and composed into DSL builders
  • curry(), memoize(), and trampoline() add functional programming superpowers
  • Every closure has this, owner, and delegate references that control method resolution

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 Closure Parameters, Delegate, and Owner

Frequently Asked Questions

What is a closure in Groovy?

A closure in Groovy is an open, anonymous block of code wrapped in curly braces { } that can take arguments, return a value, and be assigned to a variable. It is an instance of groovy.lang.Closure and can capture variables from its surrounding scope. Closures are first-class objects in Groovy, meaning you can store them in variables, pass them as method arguments, and return them from other closures.

What is the difference between a closure and a method in Groovy?

A closure is an anonymous block of code that can be assigned to a variable and passed around as data, while a method is a named member of a class. Closures capture variables from their enclosing scope, support the implicit ‘it’ parameter, have delegate resolution for DSLs, and support currying and composition operators (<< and >>). Methods can be converted to closures using the .& operator (e.g., this.&methodName).

What does ‘it’ mean in a Groovy closure?

‘it’ is the implicit default parameter name for closures that accept a single argument. When you write { println it }, Groovy treats it as equivalent to { singleParam -> println singleParam }. If the closure is called without an argument, ‘it’ is null. To explicitly declare a zero-argument closure (with no implicit ‘it’), use the syntax { -> body } with an arrow but no parameters.

How do Groovy closures differ from Java lambdas?

Groovy closures are more powerful than Java lambdas in several ways: they can capture and modify mutable variables (Java requires effectively final), they have a delegate mechanism for DSL support, they support built-in currying (curry, rcurry, ncurry), composition operators (<< and >>), memoization, and trampoline for tail-call optimization. Java lambdas are lighter-weight and restricted to SAM interfaces, while Groovy closures are full objects extending groovy.lang.Closure.

Why does return inside a Groovy closure not exit the enclosing method?

In Groovy, return inside a closure only exits the closure itself, not the enclosing method. This is because each closure invocation is a separate method call on a Closure object. When used with each(), a return acts like ‘continue’ (skips to the next iteration), not ‘break’. To exit early from iteration, use find(), any(), or every() instead of each(). For full loop control with break support, use a traditional for-loop.

Previous in Series: Groovy Sleep and Timing Examples

Next in Series: Groovy Closure Parameters, Delegate, and Owner

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 *