Groovy Closure Parameters – it, delegate, owner Explained with 10 Examples

Groovy closure delegate and parameters: it, delegate, owner, and thisObject explained with 10+ examples. Covers delegation strategies and DSL patterns. Tested on Groovy 5.x.

“A closure without understanding its delegate is like a letter without knowing the recipient — it might arrive somewhere, but probably not where you intended.”

Dierk König, Groovy in Action

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

Closures in Groovy go far deeper than anonymous blocks of code you pass around. They come with special properties – it, delegate, owner, and thisObject – that control how method calls and property references are resolved. The groovy closure delegate mechanism in particular is what powers every Groovy DSL, from Gradle build scripts to Spock test specifications.

Understanding these parameters is not just academic. The groovy closure delegate mechanism is what powers every Groovy DSL you have ever used, from Gradle build scripts to Spock test specifications. And the groovy closure it parameter is something you will use in literally every Groovy program. If you want to write idiomatic Groovy code and understand why frameworks like Gradle work the way they do, this post is essential reading.

We will walk through 10 tested examples that cover every aspect of closure parameters, from the implicit it to advanced delegation strategies. Every example includes actual output so you can follow along confidently.

The Implicit it Parameter

When a closure takes exactly one parameter and you do not bother to name it, Groovy gives it the default name it. This is one of the most widely used Groovy conventions and it makes your code remarkably concise.

Think of it as Groovy saying: “You clearly expect one argument here, so I will call it it unless you tell me otherwise.” It is not a keyword — it is simply a default parameter name. You can always replace it with an explicit parameter name when clarity matters.

Named Parameters and Multiple Parameters

While it is convenient for single-parameter closures, you will often need multiple parameters. Groovy closures support typed or untyped parameter lists, default values, and even varargs. Once you declare explicit parameters, the implicit it disappears.

Understanding owner, delegate, and thisObject

Every Groovy closure carries three special properties that determine how it resolves references to variables and methods that are not defined inside the closure itself:

  • thisObject – always refers to the enclosing class instance where the closure is defined. It never changes.
  • owner – refers to the enclosing object, which is either the enclosing class or the enclosing closure if the closure is nested. It also never changes.
  • delegate – by default, points to the same object as owner. But unlike owner and thisObject, the delegate can be changed at any time. This is the property that makes Groovy DSLs possible.

According to the official Groovy closures documentation, these three properties work together with a resolution strategy to determine where unresolved references are looked up. Understanding this mechanism is the key to mastering Groovy closures.

The following diagram shows how thisObject, owner, and delegate relate to the enclosing class and nested closures:

Inner Closure (nested)

owner = outer closure
(enclosing closure)

delegate = outer closure
(same as owner by default)

thisObject = MyClass instance
(still the class, never changes)

Outer Closure

owner = MyClass instance
(enclosing class)

delegate = MyClass instance
(same as owner by default,
but CAN be changed)

Enclosing Class (MyClass)

thisObject
= MyClass instance
(never changes)

Groovy Closure – thisObject, owner, delegate

10 Practical Examples

Let us work through each concept with real code. Every example has been tested on Groovy 5.x with actual output shown.

Example 1: The Implicit it Parameter in Action

The most common use of it is in collection operations, but it works anywhere a closure receives a single argument.

Example 1: Implicit it Parameter

// The implicit 'it' parameter in a single-parameter closure
def greet = { println "Hello, ${it}!" }
greet("Alice")
greet("Bob")

// 'it' works in collection methods
def numbers = [1, 2, 3, 4, 5]
numbers.each { println "Number: ${it}" }

// 'it' in findAll
def evens = numbers.findAll { it % 2 == 0 }
println "Evens: ${evens}"

// 'it' in collect (transform)
def doubled = numbers.collect { it * 2 }
println "Doubled: ${doubled}"

// 'it' with conditional logic
def classify = { it > 3 ? "big" : "small" }
println "5 is ${classify(5)}"
println "1 is ${classify(1)}"

Output

Hello, Alice!
Hello, Bob!
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Evens: [2, 4]
Doubled: [2, 4, 6, 8, 10]
5 is big
1 is small

Notice that it automatically takes on whatever single argument is passed to the closure. You never have to declare it — Groovy just knows.

Example 2: Replacing it with Named Parameters

While it is convenient, named parameters make your code more readable. As soon as you declare an explicit parameter, it is no longer available.

Example 2: Named Parameters vs it

// Using 'it' - concise but sometimes unclear
def numbers = [10, 20, 30]
numbers.each { println it }

println "---"

// Using a named parameter - more descriptive
numbers.each { number -> println number }

println "---"

// Named parameters shine with complex closures
def people = [
    [name: "Alice", age: 30],
    [name: "Bob", age: 25],
    [name: "Carol", age: 35]
]

// With 'it' - you have to know what 'it' is
people.each { println "${it.name} is ${it.age} years old" }

println "---"

// With named parameter - self-documenting
people.each { person -> println "${person.name} is ${person.age} years old" }

// Typed parameters for extra safety
people.each { Map person ->
    println "${person.name} works here"
}

// Zero-arg closure: use { -> } to explicitly say "no parameters"
def sayHello = { -> println "Hello with no args!" }
sayHello()
// sayHello("ignored") // This would throw an error!

Output

10
20
30
---
10
20
30
---
Alice is 30 years old
Bob is 25 years old
Carol is 35 years old
---
Alice is 30 years old
Bob is 25 years old
Carol is 35 years old
Alice works here
Bob works here
Carol works here
Hello with no args!

The arrow notation -> separates parameter declarations from the closure body. Using { -> } with no parameters on the left side explicitly declares a zero-argument closure, which means it will not be available inside.

Example 3: Multiple Parameters and Default Values

Closures can accept any number of parameters. You can also assign default values just like method parameters.

Example 3: Multiple Parameters

// Two parameters - common with eachWithIndex and Map.each
def fruits = ["apple", "banana", "cherry"]
fruits.eachWithIndex { fruit, index ->
    println "${index}: ${fruit}"
}

println "---"

// Map.each receives key-value pairs
def config = [host: "localhost", port: 8080, debug: true]
config.each { key, value ->
    println "${key} = ${value}"
}

println "---"

// Custom closure with multiple parameters
def calculate = { double a, double b, String op ->
    switch (op) {
        case '+': return a + b
        case '-': return a - b
        case '*': return a * b
        case '/': return a / b
        default: return "Unknown op: ${op}"
    }
}

println "10 + 3 = ${calculate(10, 3, '+')}"
println "10 - 3 = ${calculate(10, 3, '-')}"
println "10 * 3 = ${calculate(10, 3, '*')}"
println "10 / 3 = ${calculate(10, 3, '/')}"

println "---"

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

greet("Alice")
greet("Bob", "Hey")
greet("Carol", "Good morning")

Output

0: apple
1: banana
2: cherry
---
host = localhost
port = 8080
debug = true
---
10 + 3 = 13.0
10 - 3 = 7.0
10 * 3 = 30.0
10 / 3 = 3.3333333333333335
---
Hello, Alice!
Hey, Bob!
Good morning, Carol!

When using Map.each, Groovy automatically destructures the entry into key and value if you declare two parameters. With a single parameter (or it), you get a Map.Entry object instead.

Example 4: Exploring thisObject, owner, and delegate

Let us see exactly what each of these three properties points to. This example makes the relationships crystal clear.

Example 4: thisObject, owner, delegate

class ClosureExplorer {
    String name = "Explorer"

    void explore() {
        def outerClosure = {
            println "=== Outer Closure ==="
            println "thisObject class: ${thisObject.getClass().simpleName}"
            println "owner class:      ${owner.getClass().simpleName}"
            println "delegate class:   ${delegate.getClass().simpleName}"
            println "thisObject.name:  ${thisObject.name}"

            // Are they the same object?
            println "owner == thisObject: ${owner.is(thisObject)}"
            println "delegate == owner:   ${delegate.is(owner)}"

            def innerClosure = {
                println "\n=== Inner (Nested) Closure ==="
                println "thisObject class: ${thisObject.getClass().simpleName}"
                println "owner class:      ${owner.getClass().simpleName}"
                println "delegate class:   ${delegate.getClass().simpleName}"

                // In nested closures, owner changes!
                println "owner == thisObject: ${owner.is(thisObject)}"
                println "delegate == owner:   ${delegate.is(owner)}"
            }
            innerClosure()
        }
        outerClosure()
    }
}

new ClosureExplorer().explore()

Output

=== Outer Closure ===
thisObject class: ClosureExplorer
owner class:      ClosureExplorer
delegate class:   ClosureExplorer
thisObject.name:  Explorer
owner == thisObject: true
delegate == owner:   true

=== Inner (Nested) Closure ===
thisObject class: ClosureExplorer
owner class:      _explore_closure1
delegate class:   _explore_closure1
owner == thisObject: false
delegate == owner:   true

This is the critical insight: for the outer closure, all three properties point to the same ClosureExplorer instance. But for the inner closure, thisObject still points to ClosureExplorer (it never changes), while owner and delegate now point to the enclosing outer closure. The thisObject always refers to the class, but owner tracks the immediate enclosing scope.

Example 5: Changing the Delegate

Unlike owner and thisObject, the delegate property is mutable. You can reassign it to any object, and unresolved method calls and property accesses inside the closure will be routed to the new delegate. This is the foundation of every Groovy DSL.

Example 5: Changing the Delegate

class Person {
    String name
    int age

    String toString() { "${name} (age ${age})" }
}

class Robot {
    String name
    int age
    String model = "T-1000"

    String toString() { "${name} - Model ${model} (age ${age})" }
}

// A closure that references 'name' and 'age' without qualifying them
def describe = {
    println "Name: ${name}, Age: ${age}"
}

// Point the delegate to a Person
def person = new Person(name: "Alice", age: 30)
describe.delegate = person
describe.resolveStrategy = Closure.DELEGATE_FIRST
describe()

// Now point the same closure to a Robot
def robot = new Robot(name: "Robo", age: 5)
describe.delegate = robot
describe()

// The closure itself did not change -- only the delegate did
println "\nDelegate is now: ${describe.delegate}"
println "Owner is still:  ${describe.owner.getClass().simpleName}"

Output

Name: Alice, Age: 30
Name: Robo, Age: 5

Delegate is now: Robo - Model T-1000 (age 5)
Owner is still:  Script1

The name and age references inside the closure are unresolved locally, so Groovy looks them up on the delegate. By swapping the delegate, the same closure operates on completely different objects. This is powerful and is exactly how Gradle’s dependencies { } block works behind the scenes.

Example 6: Closure Resolve Strategies

When Groovy encounters an unresolved reference inside a closure, it uses a resolve strategy to decide where to look. The strategy determines the search order among thisObject, owner, and delegate.

Example 6: Resolve Strategies

class Owner {
    String location = "Owner's location"

    def createClosure() {
        return { "Location: ${location}" }
    }
}

class Delegate {
    String location = "Delegate's location"
}

def ownerObj = new Owner()
def closure = ownerObj.createClosure()
def delegateObj = new Delegate()
closure.delegate = delegateObj

// OWNER_FIRST (default) - checks owner before delegate
closure.resolveStrategy = Closure.OWNER_FIRST
println "OWNER_FIRST:    ${closure()}"

// DELEGATE_FIRST - checks delegate before owner
closure.resolveStrategy = Closure.DELEGATE_FIRST
println "DELEGATE_FIRST: ${closure()}"

// OWNER_ONLY - only checks owner, ignores delegate
closure.resolveStrategy = Closure.OWNER_ONLY
println "OWNER_ONLY:     ${closure()}"

// DELEGATE_ONLY - only checks delegate, ignores owner
closure.resolveStrategy = Closure.DELEGATE_ONLY
println "DELEGATE_ONLY:  ${closure()}"

// Summary of strategy constants
println "\n--- Strategy Constants ---"
println "OWNER_FIRST:    ${Closure.OWNER_FIRST}"
println "DELEGATE_FIRST: ${Closure.DELEGATE_FIRST}"
println "OWNER_ONLY:     ${Closure.OWNER_ONLY}"
println "DELEGATE_ONLY:  ${Closure.DELEGATE_ONLY}"
println "TO_SELF:        ${Closure.TO_SELF}"

Output

OWNER_FIRST:    Location: Owner's location
DELEGATE_FIRST: Location: Delegate's location
OWNER_ONLY:     Location: Owner's location
DELEGATE_ONLY:  Location: Delegate's location

--- Strategy Constants ---
OWNER_FIRST:    0
DELEGATE_FIRST: 1
OWNER_ONLY:     2
DELEGATE_ONLY:  3
TO_SELF:        4

The default strategy is OWNER_FIRST, which is why closures normally see variables from their enclosing scope. When building DSLs, you almost always switch to DELEGATE_FIRST so the closure body resolves against your DSL builder object.

Example 7: Varargs, Spread Operator, and Closure Coercion

Closures support varargs (variable-length argument lists) and can be coerced into single-abstract-method (SAM) interfaces — useful when working with Java APIs.

Example 7: Varargs and Coercion

// Varargs in closures
def sum = { int... nums ->
    nums.sum()
}

println "Sum of 1,2,3:   ${sum(1, 2, 3)}"
println "Sum of 10,20:   ${sum(10, 20)}"
println "Sum of single:  ${sum(42)}"

// Mixed parameters with varargs
def log = { String level, String... messages ->
    messages.each { msg ->
        println "[${level}] ${msg}"
    }
}

log("INFO", "Server started", "Port 8080", "Ready")
log("ERROR", "Connection failed")

println "---"

// Closure coercion to a Java interface (SAM type)
def fruits = ["banana", "apple", "cherry", "date"]

// Closure as a Comparator (Java SAM interface)
fruits.sort { a, b -> a.length() <=> b.length() }
println "Sorted by length: ${fruits}"

// Closure as a Runnable
def task = { println "Running as Runnable!" } as Runnable
new Thread(task).start()
Thread.sleep(100)

// Closure as a Callable
def callable = { return "Result from Callable" } as java.util.concurrent.Callable
println "Callable result: ${callable.call()}"

Output

Sum of 1,2,3:   6
Sum of 10,20:   30
Sum of single:  42
[INFO] Server started
[INFO] Port 8080
[INFO] Ready
[ERROR] Connection failed
---
Sorted by length: [date, apple, banana, cherry]
Running as Runnable!
Callable result: Result from Callable

The as keyword coerces a closure into any interface with a single abstract method. This makes Groovy closures interoperable with Java functional interfaces, including Runnable, Callable, Comparator, and Java 8+ functional interfaces.

Example 8: Delegate in with() and tap()

Groovy’s with() and tap() methods are built-in examples of delegate manipulation. Both set the closure’s delegate to the receiving object, but they differ in what they return.

Example 8: with() and tap()

class Server {
    String host
    int port
    boolean ssl
    List<String> routes = []

    void addRoute(String path) { routes << path }

    String toString() {
        "Server(${ssl ? 'https' : 'http'}://${host}:${port}, routes=${routes})"
    }
}

// with() - sets delegate, returns the closure's result
def info = new Server().with {
    host = "api.example.com"
    port = 443
    ssl = true
    addRoute("/users")
    addRoute("/orders")
    // The return value of with() is whatever this closure returns
    "Configured: " + delegate.toString()
}

println info
println "Type of info: ${info.getClass().simpleName}"

println "---"

// tap() - sets delegate, returns the OBJECT itself (not the closure result)
def server = new Server().tap {
    host = "localhost"
    port = 8080
    ssl = false
    addRoute("/health")
    addRoute("/metrics")
    // Whatever we return here is IGNORED -- tap() returns the object
}

println server
println "Type of server: ${server.getClass().simpleName}"

println "---"

// tap() is great for builder-style chains
def prodServer = new Server().tap {
    host = "prod.example.com"
    port = 443
    ssl = true
}.tap {
    addRoute("/api/v1")
    addRoute("/api/v2")
}

println "Chained tap: ${prodServer}"

Output

Configured: Server(https://api.example.com:443, routes=[/users, /orders])
Type of info: String
---
Server(http://localhost:8080, routes=[/health, /metrics])
Type of server: Server
---
Chained tap: Server(https://prod.example.com:443, routes=[/api/v1, /api/v2])

Use with() when you want to compute a value from an object. Use tap() when you want to configure an object and keep a reference to it. Both methods set delegate to the receiver and use DELEGATE_FIRST resolution, which is why you can reference host, port, and addRoute() without any qualifier.

Example 9: Building a Simple DSL with Delegate

Now let us put everything together and build a mini DSL. This is the pattern used by Gradle, Jenkins Pipeline, and most Groovy frameworks.

Example 9: Building a DSL

class HtmlBuilder {
    StringBuilder sb = new StringBuilder()
    int indent = 0

    void html(@DelegatesTo(HtmlBuilder) Closure body) {
        sb.append("<html>\n")
        indent++
        body.delegate = this
        body.resolveStrategy = Closure.DELEGATE_FIRST
        body()
        indent--
        sb.append("</html>")
    }

    void head(@DelegatesTo(HtmlBuilder) Closure body) {
        sb.append("${'  ' * indent}<head>\n")
        indent++
        body.delegate = this
        body.resolveStrategy = Closure.DELEGATE_FIRST
        body()
        indent--
        sb.append("${'  ' * indent}</head>\n")
    }

    void body(@DelegatesTo(HtmlBuilder) Closure body) {
        sb.append("${'  ' * indent}<body>\n")
        indent++
        body.delegate = this
        body.resolveStrategy = Closure.DELEGATE_FIRST
        body()
        indent--
        sb.append("${'  ' * indent}</body>\n")
    }

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

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

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

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

// Using the DSL
def builder = new HtmlBuilder()
builder.html {
    head {
        title "My Groovy Page"
    }
    body {
        h1 "Welcome!"
        p "This HTML was built using Groovy closure delegation."
        p "The delegate pattern makes DSLs feel natural."
    }
}

println builder

Output

<html>
  <head>
    <title>My Groovy Page</title>
  </head>
  <body>
    <h1>Welcome!</h1>
    <p>This HTML was built using Groovy closure delegation.</p>
    <p>The delegate pattern makes DSLs feel natural.</p>
  </body>
</html>

The @DelegatesTo annotation is optional but important: it tells the IDE and the static type checker what the delegate type will be, so you get autocompletion and compile-time checks. The key pattern is: set delegate, set resolveStrategy to DELEGATE_FIRST, and call the closure.

Example 10: Closure Scope and Variable Resolution Order

This final example demonstrates exactly how Groovy resolves variables by walking through the resolution chain step by step.

Example 10: Variable Resolution Order

class MyClass {
    String source = "from MyClass"
}

class MyDelegate {
    String source = "from MyDelegate"
}

class ResolutionDemo {
    String source = "from ResolutionDemo (this)"

    void demo() {
        def localSource = "from local variable"
        def myDelegate = new MyDelegate()

        def closure = {
            // Local variables always win -- they are resolved first
            // Comment out the next line to see delegate resolution
            // def source = "from inside closure"
            println "Resolved source: ${source}"
        }

        // Test 1: Default (OWNER_FIRST) -- owner is ResolutionDemo
        println "--- OWNER_FIRST (default) ---"
        closure.delegate = myDelegate
        closure.resolveStrategy = Closure.OWNER_FIRST
        closure()

        // Test 2: DELEGATE_FIRST
        println "--- DELEGATE_FIRST ---"
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        closure()

        // Test 3: DELEGATE_ONLY
        println "--- DELEGATE_ONLY ---"
        closure.resolveStrategy = Closure.DELEGATE_ONLY
        closure()

        // Demonstrating that local variables ALWAYS win
        def closure2 = {
            def source = "from local inside closure"
            println "Local always wins: ${source}"
        }
        closure2.delegate = myDelegate
        closure2.resolveStrategy = Closure.DELEGATE_FIRST
        println "\n--- Local Variable Priority ---"
        closure2()
    }
}

new ResolutionDemo().demo()

Output

--- OWNER_FIRST (default) ---
Resolved source: from ResolutionDemo (this)
--- DELEGATE_FIRST ---
Resolved source: from MyDelegate
--- DELEGATE_ONLY ---
Resolved source: from MyDelegate

--- Local Variable Priority ---
Local always wins: from local inside closure

The resolution order is: (1) local variables inside the closure always win, regardless of strategy. (2) Then, depending on the strategy, either the owner or delegate is checked first. (3) If neither has the property, a MissingPropertyException is thrown. Local variables are never affected by the delegation strategy.

Delegation Strategies

Let us summarize the five delegation strategies available in Groovy, since they are central to how groovy closure delegate resolution works:

StrategyConstantResolution OrderUse Case
OWNER_FIRST0owner → delegateDefault behavior, normal closures
DELEGATE_FIRST1delegate → ownerDSLs, builders, configuration blocks
OWNER_ONLY2owner onlyStrict scoping, ignore delegate
DELEGATE_ONLY3delegate onlyStrict delegation, sandboxed closures
TO_SELF4closure itself onlyRarely used, closure must define methods

In practice, you will almost exclusively use OWNER_FIRST (default) and DELEGATE_FIRST (DSLs). The OWNER_ONLY and DELEGATE_ONLY strategies are useful when you need strict control over resolution to avoid ambiguity. TO_SELF is almost never used in practice.

The following diagram visualizes the complete delegation strategy flowchart, showing how each strategy resolves unresolved references:

Found

Not found

OWNER_FIRST (0)
default

Found

Not found

Found

Not found

DELEGATE_FIRST (1)
used in DSLs

Found

Not found

Found

Not found

OWNER_ONLY (2)

Found

Not found

DELEGATE_ONLY (3)

Found

Not found

TO_SELF (4)

Found

Not found

Unresolved reference
inside closure

Check local variables
(always wins, regardless of strategy)

Resolved

resolveStrategy?

1. Check owner

2. Check delegate

MissingPropertyException

1. Check delegate

2. Check owner

1. Check owner

1. Check delegate

1. Check closure itself

Groovy Closure – Delegation Strategies

Building DSLs with Delegate

The delegate pattern is the backbone of Groovy DSLs. Here is a practical example that shows a configuration DSL similar to what you see in Gradle:

Configuration DSL Pattern

class DatabaseConfig {
    String url
    String username
    String password
    int maxConnections = 10
    int timeout = 30

    String toString() {
        """\
        |Database Config:
        |  URL:        ${url}
        |  Username:   ${username}
        |  Max Conns:  ${maxConnections}
        |  Timeout:    ${timeout}s""".stripMargin()
    }
}

class AppConfig {
    String appName
    String version
    DatabaseConfig database = new DatabaseConfig()

    void database(@DelegatesTo(DatabaseConfig) Closure config) {
        config.delegate = database
        config.resolveStrategy = Closure.DELEGATE_FIRST
        config()
    }

    String toString() {
        "${appName} v${version}\n${database}"
    }
}

// Using it like a DSL -- clean, readable configuration
def app = new AppConfig()
app.with {
    appName = "MyApp"
    version = "2.0"

    database {
        url = "jdbc:postgresql://localhost:5432/mydb"
        username = "admin"
        password = "secret123"
        maxConnections = 25
        timeout = 60
    }
}

println app

Output

MyApp v2.0
Database Config:
  URL:        jdbc:postgresql://localhost:5432/mydb
  Username:   admin
  Max Conns:  25
  Timeout:    60s

This is exactly the pattern that Gradle uses for its build configuration. When you write dependencies { implementation 'some:lib:1.0' } in a Gradle build file, Gradle sets the closure’s delegate to a DependencyHandler object. That is why implementation resolves as a method call on that handler.

Common Pitfalls

Even experienced Groovy developers stumble on these closure parameter issues. Here are the traps to watch for:

Pitfall 1: Forgetting the Arrow for Zero-Arg Closures

Zero-Arg Closure Pitfall

// WRONG: This closure accepts one parameter (it)
def wrong = { println "it is: ${it}" }
wrong("surprise!")  // prints "it is: surprise!"

// RIGHT: Use the arrow to declare zero parameters
def right = { -> println "No params here" }
right()  // works fine
// right("surprise!")  // ERROR: too many arguments

Output

it is: surprise!
No params here

Pitfall 2: Assuming delegate Changes Affect owner

Changing the delegate does not change the owner. The owner is fixed at closure creation time. If your resolve strategy is OWNER_FIRST (the default) and the owner has the property you are looking for, the delegate will never be consulted.

Pitfall 3: Shadowing with Local Variables

Local variables inside a closure always take priority over both owner and delegate properties, regardless of the resolution strategy. If you define a local variable with the same name as a delegate property, the delegate property becomes invisible.

For more advanced closure techniques including functional composition, check out our upcoming post on Groovy higher-order functions.

Conclusion

We have covered the full range of Groovy closure parameters — from the convenient implicit it to the powerful delegation mechanism that enables DSLs. The three special properties (thisObject, owner, delegate) and the five resolution strategies give you complete control over how closures resolve unqualified references.

The delegate pattern is particularly important because it is the engine behind Gradle, Spock, Grails, and virtually every Groovy framework. Once you understand how delegation works, those framework configurations stop being magic and start being obvious.

Summary

  • it is the implicit name for a single-parameter closure — convenient but sometimes unclear
  • Use { -> } to explicitly declare a zero-parameter closure and disable it
  • thisObject always points to the enclosing class and never changes
  • owner points to the enclosing object (class or outer closure) and never changes
  • delegate is mutable and defaults to the same as owner — reassign it to build DSLs
  • Use DELEGATE_FIRST strategy when building DSLs and configuration blocks
  • Local variables inside the closure always win, regardless of resolution strategy
  • The @DelegatesTo annotation enables IDE support and static type checking

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 Higher-Order Functions – collect, inject, groupBy

Frequently Asked Questions

What is the it parameter in Groovy closures?

The it parameter is an implicit default name that Groovy assigns to the single parameter of a closure when you do not declare an explicit parameter name. For example, { println it } is equivalent to { x -> println x }. If the closure has zero or more than one parameter, it is not available. You can disable it by using the arrow syntax with no parameters: { -> println ‘no it here’ }.

What is the difference between delegate and owner in a Groovy closure?

The owner is set when the closure is created and always points to the enclosing object (either the enclosing class instance or the enclosing closure if nested). It cannot be changed. The delegate defaults to the same object as the owner, but it can be reassigned to any object at any time. When a closure encounters an unresolved method or property, the resolve strategy determines whether to check the owner or delegate first.

How does the Groovy closure delegate enable DSLs?

When you set a closure’s delegate to a builder or configuration object and use DELEGATE_FIRST resolution strategy, all unresolved method calls and property accesses inside the closure are forwarded to that delegate object. This lets you write configuration blocks like dependencies { implementation ‘lib:1.0’ } where implementation looks like a language keyword but is actually a method on the delegate object. This pattern is used by Gradle, Spock, Grails, and most Groovy frameworks.

What are the five closure resolve strategies in Groovy?

Groovy provides five resolve strategies: OWNER_FIRST (default, checks owner then delegate), DELEGATE_FIRST (checks delegate then owner, used for DSLs), OWNER_ONLY (ignores delegate completely), DELEGATE_ONLY (ignores owner completely), and TO_SELF (only resolves against the closure itself). In practice, OWNER_FIRST and DELEGATE_FIRST are the most commonly used strategies.

Can I use typed parameters in Groovy closures?

Yes, Groovy closures support typed parameters just like method parameters. You can write { String name, int age -> println${name} is ${age}” }. Typed parameters provide better documentation, enable IDE autocompletion, and catch type mismatches at compile time when using @CompileStatic. You can also use default values: { String name, String greeting = ‘Hello’ -> println${greeting}, ${name}” }.

Previous in Series: Groovy Closures – The Complete Guide

Next in Series: Groovy Higher-Order Functions – collect, inject, groupBy

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 *