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.
Table of Contents
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:
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:
| Strategy | Constant | Resolution Order | Use Case |
|---|---|---|---|
| OWNER_FIRST | 0 | owner → delegate | Default behavior, normal closures |
| DELEGATE_FIRST | 1 | delegate → owner | DSLs, builders, configuration blocks |
| OWNER_ONLY | 2 | owner only | Strict scoping, ignore delegate |
| DELEGATE_ONLY | 3 | delegate only | Strict delegation, sandboxed closures |
| TO_SELF | 4 | closure itself only | Rarely 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:
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
itis the implicit name for a single-parameter closure — convenient but sometimes unclear- Use
{ -> }to explicitly declare a zero-parameter closure and disableit thisObjectalways points to the enclosing class and never changesownerpoints to the enclosing object (class or outer closure) and never changesdelegateis mutable and defaults to the same as owner — reassign it to build DSLs- Use
DELEGATE_FIRSTstrategy when building DSLs and configuration blocks - Local variables inside the closure always win, regardless of resolution strategy
- The
@DelegatesToannotation 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}” }.
Related Posts
Previous in Series: Groovy Closures – The Complete Guide
Next in Series: Groovy Higher-Order Functions – collect, inject, groupBy
Related Topics You Might Like:
- Groovy Closures – The Complete Guide
- Groovy Higher-Order Functions – collect, inject, groupBy
- Groovy Each Loop Examples
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment