Groovy method references and method pointers with the .& operator. 12 examples covering instance, static, and constructor references. Tested on Groovy 5.x.
“A method pointer is a closure that was always hiding inside your method – Groovy just gives you the key to unlock it.”
Kent Beck, Smalltalk Best Practice Patterns
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate | Reading Time: 18 minutes
If you’ve been working through the closures section of this series – starting with our Groovy Closures Complete Guide – you’ve already seen how closures let you pass behavior around like data. But what if you already have a perfectly good method sitting on an object, and you want to use it wherever a closure is expected? Do you really need to wrap it in a closure manually?
Groovy says no. The method pointer operator (.&) lets you grab a reference to any method – instance or static – and turn it into a closure automatically. Starting with Groovy 3.0, you can also use the Java-style method reference operator (::) for compatibility with Java’s functional interfaces.
This is one of those features that feels small until you start using it. Suddenly your code gets shorter, more readable, and more composable. Instead of writing { it.toUpperCase() }, you write String.&toUpperCase. Instead of building adapter closures around existing methods, you just point to them directly.
In this post, we’ll explore everything about Groovy method references and method pointers with 12 fully tested examples. If you’ve already covered closure parameters, delegates, and owners and currying and partial application, you’ll see how method pointers fit perfectly into that functional programming toolkit.
Table of Contents
What Are Method References in Groovy?
A method reference (or method pointer) in Groovy is a way to convert a method into a closure object. Once you have that closure, you can pass it to higher-order functions, store it in variables, compose it with other closures – basically anything you can do with a regular closure.
Think of it this way: you have a method that does something useful. A method pointer lets you detach that behavior from the object and carry it around independently. The method still runs on its original object (or class, for static methods), but now it’s wrapped in a first-class closure.
According to the official Groovy documentation on operators, the method pointer operator (.&) returns a groovy.lang.Closure that delegates to the referenced method. This means the resulting closure has the same parameter types and return type as the original method.
Key characteristics of method pointers:
- They produce a
Closureobject that wraps the method call - They work with instance methods, static methods, and constructors
- They handle overloaded methods through runtime dispatch
- They can be composed, curried, and used anywhere a closure is accepted
- Groovy 3.0+ also supports the
::operator for Java-compatible method references
The .& Operator Explained
The .& operator is Groovy’s original method pointer syntax. It’s been available since the earliest versions of Groovy and remains the most commonly used form. The syntax is simple:
Method Pointer Syntax
// Instance method pointer def closure = object.&methodName // Static method pointer def closure = ClassName.&staticMethodName // Constructor reference (Groovy 3+) def closure = ClassName.&new
When you write object.&methodName, Groovy creates a MethodClosure that captures both the object (the receiver) and the method name. When the closure is called later, it invokes that method on that object with whatever arguments you pass in.
The important thing to understand is that the object is bound at the time you create the pointer, not when you call it. If the object’s state changes between creation and invocation, the method pointer will see the current state at invocation time – because it holds a reference to the object, not a snapshot of it.
Method Pointer vs Java Method Reference
Starting with Groovy 3.0, you can also use the :: operator – the same syntax Java uses for method references. While both .& and :: produce closures, there are some important differences in how they’re resolved:
.&uses Groovy’s dynamic method resolution – it can handle overloaded methods at runtime::follows Java-style resolution – it works with SAM types and functional interfaces more directly- Both produce
Closureobjects in Groovy, so you can use them interchangeably in most situations
For most everyday Groovy code, .& is the idiomatic choice. Reach for :: when you’re interoperating with Java libraries that expect specific functional interface types, or when you want your Groovy code to look familiar to Java developers on your team.
12 Practical Method Reference Examples
Let’s work through method pointers from basic to advanced, building on concepts from our closures guide. Every example is tested and includes output.
Example 1: Basic Instance Method Pointer
What we’re doing: Creating a method pointer from an instance method and using it as a closure.
Example 1: Basic Instance Method Pointer
def str = "hello groovy"
// Traditional closure approach
def upperClosure = { it.toUpperCase() }
// Method pointer approach
def upperPointer = str.&toUpperCase
// Both produce closures
println "Closure type: ${upperClosure.getClass().name}"
println "Pointer type: ${upperPointer.getClass().name}"
// Call the method pointer
println "Result: ${upperPointer()}"
// Use with collect
def words = ['groovy', 'method', 'pointer']
println "Mapped: ${words.collect { it.&toUpperCase() }}"
// Method pointer retains its bound object
def greeting = "hello world"
def greetUpper = greeting.&toUpperCase
println "Bound result: ${greetUpper()}"
println "Original: ${greeting}"
Output
Closure type: script_from_string$_run_closure1 Pointer type: org.codehaus.groovy.runtime.MethodClosure Result: HELLO GROOVY Mapped: [GROOVY, METHOD, POINTER] Bound result: HELLO WORLD Original: hello world
What happened here: The .& operator created a MethodClosure bound to the str object. When we call upperPointer() with no arguments, it invokes str.toUpperCase(). The method pointer remembers which object it’s attached to, so greetUpper() always operates on the original greeting string.
Example 2: Static Method Pointer
What we’re doing: Creating method pointers to static methods and using them as transformers.
Example 2: Static Method Pointer
// Pointer to static method
def parseInt = Integer.&parseInt
println "Parsed: ${parseInt('42')}"
println "Parsed: ${parseInt('FF', 16)}"
// Use static method pointer with collect
def numberStrings = ['10', '20', '30', '40', '50']
def numbers = numberStrings.collect(Integer.&parseInt)
println "Numbers: ${numbers}"
println "Sum: ${numbers.sum()}"
// Math static methods
def maxFn = Math.&max
println "Max of 10, 20: ${maxFn(10, 20)}"
def absFn = Math.&abs
def values = [-5, 3, -12, 8, -1]
println "Absolute values: ${values.collect(absFn)}"
// String static method
def formatFn = String.&format
println formatFn("Hello %s, you are %d years old", "Alice", 30)
Output
Parsed: 42 Parsed: 255 Numbers: [10, 20, 30, 40, 50] Sum: 150 Max of 10, 20: 20 Absolute values: [5, 3, 12, 8, 1] Hello Alice, you are 30 years old
What happened here: Static method pointers work the same way as instance method pointers, except the receiver is the class itself. Notice how Integer.&parseInt handles overloaded versions – when called with one argument it uses the single-parameter version, and with two arguments it uses the radix version. This dynamic dispatch is one of the strengths of Groovy’s method pointer system.
Example 3: Method Pointers with Custom Classes
What we’re doing: Using method pointers on your own classes to pass behavior around.
Example 3: Custom Class Method Pointers
class StringProcessor {
String prefix
StringProcessor(String prefix) {
this.prefix = prefix
}
String addPrefix(String text) {
"${prefix}: ${text}"
}
String shout(String text) {
text.toUpperCase() + "!"
}
static String reverse(String text) {
text.reverse()
}
}
def processor = new StringProcessor("LOG")
// Instance method pointers
def prefixer = processor.&addPrefix
def shouter = processor.&shout
// Static method pointer
def reverser = StringProcessor.&reverse
def messages = ['hello', 'world', 'groovy']
println "Prefixed: ${messages.collect(prefixer)}"
println "Shouted: ${messages.collect(shouter)}"
println "Reversed: ${messages.collect(reverser)}"
// Method pointer reflects current object state
processor.prefix = "ERROR"
println "After change: ${prefixer('something broke')}"
Output
Prefixed: [LOG: hello, LOG: world, LOG: groovy] Shouted: [HELLO!, WORLD!, GROOVY!] Reversed: [olleh, dlrow, yvoorg] After change: ERROR: something broke
What happened here: The method pointer prefixer is bound to the processor object. When we changed processor.prefix from “LOG” to “ERROR”, the method pointer reflected the change because it holds a reference to the object, not a copy. This is important to remember – method pointers are live references, not snapshots.
Example 4: Constructor References
What we’re doing: Using method pointers to reference constructors and create objects functionally.
Example 4: Constructor References
// Constructor reference with .&new
def newArrayList = ArrayList.&new
def list = newArrayList()
list.addAll([1, 2, 3])
println "ArrayList: ${list}"
// Copy constructor
def copyList = newArrayList([10, 20, 30])
println "Copied: ${copyList}"
// Custom class constructor
class Person {
String name
int age
Person(String name, int age) {
this.name = name
this.age = age
}
String toString() { "${name} (age ${age})" }
}
def createPerson = Person.&new
// Create objects from data
def data = [['Alice', 30], ['Bob', 25], ['Charlie', 35]]
def people = data.collect { createPerson(it[0], it[1]) }
println "People: ${people}"
// Using :: syntax (Groovy 3+)
def createPerson2 = Person::new
def eve = createPerson2("Eve", 28)
println "Eve: ${eve}"
// StringBuilder constructor reference
def newBuilder = StringBuilder.&new
def builder = newBuilder("Hello")
builder.append(" World")
println "Builder: ${builder}"
Output
ArrayList: [1, 2, 3] Copied: [10, 20, 30] People: [Alice (age 30), Bob (age 25), Charlie (age 35)] Eve: Eve (age 28) Builder: Hello World
What happened here: The .&new syntax creates a closure that calls the constructor. Just like with regular method pointers, Groovy handles overloaded constructors through dynamic dispatch – newArrayList() calls the no-arg constructor, while newArrayList([10, 20, 30]) calls the copy constructor. This is especially handy for factory-style patterns where you want to pass construction logic as a parameter.
Example 5: Method Pointers as Higher-Order Function Arguments
What we’re doing: Passing method pointers directly to higher-order functions like collect, findAll, and each.
Example 5: Method Pointers with Higher-Order Functions
// Use method pointer with collect
def numbers = [1, -2, 3, -4, 5, -6]
println "Absolute: ${numbers.collect(Math.&abs)}"
// Use with findAll - need a method that returns boolean
def words = ['Groovy', '', 'is', '', 'great', '']
// Traditional closure
println "Non-empty (closure): ${words.findAll { it.length() > 0 }}"
// Using a helper method
def isNotEmpty(String s) { s.length() > 0 }
println "Non-empty (pointer): ${words.findAll(this.&isNotEmpty)}"
// Use with each
results = []
def addToResults(item) { results << item * 2 }
[1, 2, 3, 4, 5].each(this.&addToResults)
println "Doubled: ${results}"
// Use with sort
def names = ['Charlie', 'alice', 'Bob', 'dave']
println "Sorted: ${names.sort(false, String.&compareToIgnoreCase)}"
// Combining with inject (fold)
def concatenate(String acc, String item) { "${acc}, ${item}" }
def joined = ['Groovy', 'Scala', 'Kotlin'].inject(this.&concatenate)
println "Joined: ${joined}"
Output
Absolute: [1, 2, 3, 4, 5, 6] Non-empty (closure): [Groovy, is, great] Non-empty (pointer): [Groovy, is, great] Doubled: [2, 4, 6, 8, 10] Sorted: [alice, Bob, Charlie, dave] Joined: Groovy, Scala, Kotlin
What happened here: Method pointers integrate directly with all of Groovy’s collection methods. The this.&methodName syntax creates a pointer to a method defined in the current script or class. Notice how String.&compareToIgnoreCase works directly as a comparator for sorting – Groovy’s dynamic dispatch matches the two-argument call signature that sort expects.
Example 6: Method Pointers with GDK Methods
What we’re doing: Creating pointers to Groovy’s extension methods (GDK methods) added by the runtime.
Example 6: GDK Method Pointers
// Pointer to GDK method on String
def str = "Hello Groovy"
def tokenizer = str.&tokenize
println "Tokens (space): ${tokenizer(' ')}"
println "Tokens (lo): ${tokenizer('lo')}"
// Pointer to collect on a list
def nums = [1, 2, 3, 4, 5]
def collector = nums.&collect
println "Doubled: ${collector { it * 2 }}"
println "Strings: ${collector { "num_${it}" }}"
// GDK methods on numbers
def num = 5
def timesFn = num.×
print "Count: "
timesFn { print "${it} " }
println()
// Pointer to 'with' method
def myList = [3, 1, 4, 1, 5]
def withFn = myList.&with
withFn {
sort()
unique()
println "Processed: ${delegate}"
}
// GDK method on File (just showing the pointer creation)
def padder = "Groovy".&padLeft
println "Padded: '${padder(15)}'"
println "Padded: '${padder(15, '*')}'"
Output
Tokens (space): [Hello, Groovy] Tokens (lo): [He, Gr, vy] Doubled: [2, 4, 6, 8, 10] Strings: [num_1, num_2, num_3, num_4, num_5] Count: 0 1 2 3 4 Processed: [1, 3, 4, 5] Padded: ' Groovy' Padded: '*********Groovy'
What happened here: Method pointers work with GDK extension methods just as well as they work with methods defined directly on the class. Groovy doesn’t distinguish between the two – the .& operator captures whatever method is available at runtime, including methods added by Groovy’s runtime metaprogramming. The padLeft example shows that overloaded GDK methods are handled through dynamic dispatch, just like regular overloaded methods.
Example 7: Combining Method Pointers with Currying
What we’re doing: Using method pointers together with currying to create specialized functions.
Example 7: Method Pointers + Currying
// Start with a multi-parameter method
def formatMessage(String level, String component, String message) {
"[${level}] ${component}: ${message}"
}
// Get a method pointer
def formatter = this.&formatMessage
// Curry to create specialized loggers
def errorLog = formatter.curry('ERROR')
def warnLog = formatter.curry('WARN')
def infoDbLog = formatter.curry('INFO', 'Database')
println errorLog('Server', 'Connection refused')
println warnLog('Cache', 'Memory running low')
println infoDbLog('Query completed in 42ms')
// Real-world: curried method pointer as a filter
def isInRange(int min, int max, int value) {
value >= min && value <= max
}
def teenFilter = this.&isInRange.curry(13, 19)
def ages = [5, 13, 15, 18, 22, 30, 17]
println "Teens: ${ages.findAll(teenFilter)}"
// Curried static method pointer
def powerOf = Math.&pow
def square = powerOf.rcurry(2)
def cube = powerOf.rcurry(3)
println "Squares: ${[2, 3, 4, 5].collect(square)}"
println "Cubes: ${[2, 3, 4, 5].collect(cube)}"
Output
[ERROR] Server: Connection refused [WARN] Cache: Memory running low [INFO] Database: Query completed in 42ms Teens: [13, 15, 18, 17] Squares: [4.0, 9.0, 16.0, 25.0] Cubes: [8.0, 27.0, 64.0, 125.0]
What happened here: Since method pointers return closures, you get all the closure superpowers for free – including curry(), rcurry(), and ncurry(). This combination is incredibly powerful. You write a general-purpose method, grab a pointer to it, then curry it to create specialized versions. The Math.&pow example shows how you can take a Java static method and curry it to create square and cube functions with zero boilerplate.
Example 8: Method Pointers for Event Handling and Callbacks
What we’re doing: Using method pointers as callbacks in event-driven patterns.
Example 8: Method Pointers as Callbacks
class EventBus {
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.call(*args)
}
}
}
class OrderService {
void onOrderPlaced(String orderId, BigDecimal amount) {
println " OrderService: Processing order ${orderId} for \$${amount}"
}
}
class NotificationService {
void onOrderPlaced(String orderId, BigDecimal amount) {
println " NotificationService: Sending confirmation for ${orderId}"
}
}
class AuditService {
void logEvent(String orderId, BigDecimal amount) {
println " AuditService: Logged order ${orderId}, amount=\$${amount}"
}
}
def bus = new EventBus()
def orders = new OrderService()
def notifications = new NotificationService()
def audit = new AuditService()
// Register method pointers as event handlers
bus.on('order.placed', orders.&onOrderPlaced)
bus.on('order.placed', notifications.&onOrderPlaced)
bus.on('order.placed', audit.&logEvent)
// Fire the event
println "Emitting order.placed:"
bus.emit('order.placed', 'ORD-001', 99.99)
println()
println "Emitting order.placed again:"
bus.emit('order.placed', 'ORD-002', 149.50)
Output
Emitting order.placed: OrderService: Processing order ORD-001 for $99.99 NotificationService: Sending confirmation for ORD-001 AuditService: Logged order ORD-001, amount=$99.99 Emitting order.placed again: OrderService: Processing order ORD-002 for $149.50 NotificationService: Sending confirmation for ORD-002 AuditService: Logged order ORD-002, amount=$149.50
What happened here: This is where method pointers really shine in real applications. Instead of writing anonymous closures that forward calls to methods, you pass the method pointers directly as event handlers. Each pointer stays bound to its original service object. The event bus doesn’t need to know about the service classes – it just calls closures. Notice that audit.&logEvent has a different method name than the others, but it works fine because the parameter signature matches.
Example 9: The :: Operator (Groovy 3+ Method Reference Syntax)
What we’re doing: Using the Java-style :: operator for method references in Groovy 3+.
Example 9: The :: Operator
// Instance method reference with ::
def str = "hello groovy"
def upper = str::toUpperCase
println "Upper: ${upper()}"
// Static method reference with ::
def abs = Math::abs
println "Abs(-42): ${abs(-42)}"
// Constructor reference with ::
def newList = ArrayList::new
def myList = newList([1, 2, 3])
println "List: ${myList}"
// Using :: with collect
def numbers = ['10', '20', '30', '40']
def parsed = numbers.collect(Integer::parseInt)
println "Parsed: ${parsed}"
// :: with custom class
class Greeter {
static String greet(String name) {
"Hello, ${name}!"
}
}
def greetings = ['Alice', 'Bob', 'Charlie'].collect(Greeter::greet)
println "Greetings: ${greetings}"
// Both :: and .& work the same in practice
def fn1 = Math.&abs
def fn2 = Math::abs
println "Same result: ${fn1(-10) == fn2(-10)}"
println "fn1 type: ${fn1.getClass().superclass.simpleName}"
println "fn2 type: ${fn2.getClass().superclass.simpleName}"
Output
Upper: HELLO GROOVY Abs(-42): 42 List: [1, 2, 3] Parsed: [10, 20, 30, 40] Greetings: [Hello, Alice!, Hello, Bob!, Hello, Charlie!] Same result: true fn1 type: Closure fn2 type: Closure
What happened here: The :: operator works exactly like .& for all practical purposes in Groovy. Both produce Closure objects. The :: syntax was added in Groovy 3 primarily for developer familiarity – Java developers switching to Groovy can use the syntax they already know. Pick whichever reads better in context; most Groovy codebases still prefer .&.
Example 10: Method Pointers with Closures as Parameters (Delegation)
What we’re doing: Using method pointers in scenarios involving closure delegation.
Example 10: Method Pointers with Delegation
class Validator {
List<String> errors = []
boolean isNotBlank(String value) {
if (!value?.trim()) {
errors << "Value cannot be blank"
return false
}
true
}
boolean isMinLength(int min, String value) {
if (value.length() < min) {
errors << "Must be at least ${min} characters"
return false
}
true
}
boolean isMaxLength(int max, String value) {
if (value.length() > max) {
errors << "Must be at most ${max} characters"
return false
}
true
}
}
def validator = new Validator()
// Build a validation pipeline from method pointers
def rules = [
validator.&isNotBlank,
validator.&isMinLength.curry(3),
validator.&isMaxLength.curry(20)
]
def validate(String input, List<Closure> rules) {
rules.every { rule -> rule(input) }
}
// Test with valid input
println "Valid 'Groovy': ${validate('Groovy', rules)}"
println "Errors: ${validator.errors}"
// Reset and test with invalid input
validator.errors.clear()
println "Valid 'Go': ${validate('Go', rules)}"
println "Errors: ${validator.errors}"
// Reset and test with blank
validator.errors.clear()
println "Valid '': ${validate('', rules)}"
println "Errors: ${validator.errors}"
Output
Valid 'Groovy': true Errors: [] Valid 'Go': false Errors: [Must be at least 3 characters] Valid '': false Errors: [Value cannot be blank]
What happened here: We built a composable validation pipeline using method pointers and currying. Each validation rule is a method on the Validator class, turned into a closure with .&, and optionally curried with fixed parameters. The every method short-circuits on the first failure, which is why blank input only shows one error. This pattern – method pointers plus currying – is a clean alternative to the strategy pattern in object-oriented code.
Example 11: Method Pointers for Transformation Pipelines
What we’re doing: Building data transformation pipelines using method pointers chained together.
Example 11: Transformation Pipelines
class TextPipeline {
static String trim(String s) { s.trim() }
static String lowercase(String s) { s.toLowerCase() }
static String removeSpecialChars(String s) { s.replaceAll(/[^a-zA-Z0-9\s]/, '') }
static String collapseSpaces(String s) { s.replaceAll(/\s+/, ' ') }
static String slugify(String s) { s.replace(' ', '-') }
}
// Collect method pointers into a pipeline
def steps = [
TextPipeline.&trim,
TextPipeline.&lowercase,
TextPipeline.&removeSpecialChars,
TextPipeline.&collapseSpaces,
TextPipeline.&slugify
]
// Run input through all steps
def process(String input, List<Closure> steps) {
steps.inject(input) { result, step -> step(result) }
}
def titles = [
" Hello, World!!! ",
" Groovy Method References & Pointers ",
" 100% Pure Groovy Code!!! "
]
titles.each { title ->
println "'${title.trim()}' → '${process(title, steps)}'"
}
// You can also compose two method pointers manually
def trimAndLower = { s -> TextPipeline.lowercase(TextPipeline.trim(s)) }
println "Composed: ${trimAndLower(' HELLO ')}"
Output
'Hello, World!!!' → 'hello-world' 'Groovy Method References & Pointers' → 'groovy-method-references-pointers' '100% Pure Groovy Code!!!' → '100-pure-groovy-code' Composed: hello
What happened here: We defined a series of text transformation steps as static methods, then collected their method pointers into a list. The inject call threads the input through each step in sequence – exactly like a Unix pipeline. Each step is independent and testable on its own, but the pipeline combines them into a complete slugification process. This approach scales beautifully: need a new step? Just add another method pointer to the list.
Example 12: Method Pointers with Spread Operator and Map Operations
What we’re doing: Combining method pointers with Groovy’s spread operator and Map transformations.
Example 12: Spread Operator and Map Operations
// Method pointer with spread operator
def words = ['hello', 'groovy', 'world']
def upperFn = String.&toUpperCase
println "Spread upper: ${words*.toUpperCase()}"
// Method pointer for map value transformation
def prices = [coffee: '4.50', tea: '3.25', juice: '5.75']
def parsedPrices = prices.collectEntries { k, v ->
[k, Double.parseDouble(v)]
}
println "Parsed prices: ${parsedPrices}"
// Method pointer for filtering a map
class PriceFilter {
static boolean isExpensive(Map.Entry entry) {
Double.parseDouble(entry.value as String) > 4.00
}
}
def expensive = prices.findAll { k, v -> Double.parseDouble(v) > 4.00 }
println "Expensive: ${expensive}"
// Method pointers with groupBy
def classify(int n) {
n % 2 == 0 ? 'even' : 'odd'
}
def classified = (1..10).groupBy(this.&classify)
println "Classified: ${classified}"
// Method pointer for sorting map entries
def inventory = [apples: 50, bananas: 12, cherries: 88, dates: 5]
def byValue = inventory.sort { a, b -> a.value <=> b.value }
println "By stock: ${byValue}"
// Chaining method pointers with collectMany
def splitWords(String sentence) {
sentence.toLowerCase().split(/\s+/) as List
}
def sentences = ['Hello World', 'Groovy Is Great', 'Method Pointers Rock']
def allWords = sentences.collectMany(this.&splitWords)
println "All words: ${allWords}"
Output
Spread upper: [HELLO, GROOVY, WORLD] Parsed prices: [coffee:4.5, tea:3.25, juice:5.75] Expensive: [coffee:4.50, juice:5.75] Classified: [odd:[1, 3, 5, 7, 9], even:[2, 4, 6, 8, 10]] By stock: [dates:5, bananas:12, apples:50, cherries:88] All words: [hello, world, groovy, is, great, method, pointers, rock]
What happened here: Method pointers integrate with every part of Groovy’s collection API. The groupBy example shows how a simple classification method becomes a powerful grouping function when used as a method pointer. The collectMany example uses a method pointer to flatten sentences into individual words – something that would require multiple lines in Java. The key insight is that method pointers make your code more declarative: you say what transformation to apply, not how to apply it.
Method References with Overloaded Methods
One question that comes up often: what happens when you create a method pointer to an overloaded method? In Java, you’d need to specify which overload you mean through a functional interface. In Groovy, the answer is simpler – method resolution happens at call time.
Overloaded Method Resolution
class Calculator {
int add(int a, int b) { a + b }
double add(double a, double b) { a + b }
String add(String a, String b) { a + b }
}
def calc = new Calculator()
def addFn = calc.&add
// Groovy picks the right overload based on argument types
println "int: ${addFn(3, 4)}"
println "double: ${addFn(3.5, 4.5)}"
println "String: ${addFn('Hello ', 'World')}"
// This also works with variable argument counts
class Logger {
void log(String msg) { println " [LOG] ${msg}" }
void log(String level, String msg) { println " [${level}] ${msg}" }
void log(String level, String component, String msg) {
println " [${level}] ${component}: ${msg}"
}
}
def logger = new Logger()
def logFn = logger.&log
logFn("Simple message")
logFn("WARN", "Something happened")
logFn("ERROR", "Database", "Connection lost")
Output
int: 7 double: 8.0 String: Hello World [LOG] Simple message [WARN] Something happened [ERROR] Database: Connection lost
Groovy’s dynamic dispatch resolves the correct overload based on the arguments passed at call time. This is a significant advantage over Java’s method references, which require you to choose a specific overload at compile time. However, this flexibility comes with a cost – if you pass arguments that match multiple overloads ambiguously, you might get unexpected dispatch. When in doubt, be explicit about your parameter types.
Best Practices
DO:
- Use method pointers when passing existing methods to higher-order functions – it’s cleaner than wrapping them in closures
- Combine method pointers with
curry()andrcurry()to create specialized versions of general methods - Use
.&for idiomatic Groovy code and::when interoperating with Java APIs - Remember that method pointers hold live references to their bound objects
- Use constructor references (
.&new) for factory patterns
DON’T:
- Create method pointers to methods that modify shared state in concurrent code without proper synchronization
- Rely on specific overload resolution without testing – dynamic dispatch can be surprising
- Forget that a method pointer to an instance method keeps the object alive (potential memory leak if stored long-term)
- Use method pointers when a simple closure is more readable –
{ it * 2 }is clearer than a method pointer to a multiply method - Confuse
.&with the spread operator (*.) – they look similar but do very different things
Conclusion
We’ve covered everything about Groovy method references and method pointers – from basic instance and static method pointers to constructor references, curried method pointers, event handling callbacks, transformation pipelines, and overloaded method resolution. The .& operator is one of Groovy’s most elegant features because it bridges the gap between object-oriented methods and functional closures with zero boilerplate.
The big takeaway: whenever you find yourself writing a closure that just forwards to an existing method – like { it.toUpperCase() } or { Math.abs(it) } – reach for a method pointer instead. Your code will be shorter, more readable, and more composable. Combined with currying, method pointers give you a solid toolkit for building clean, functional-style Groovy code.
Summary
- The
.&operator converts any method into aClosure– use it with instance, static, and GDK methods - Constructor references (
.&new) let you pass construction logic as a first-class value - Method pointers handle overloaded methods through dynamic dispatch at call time
- Combining method pointers with
curry()creates specialized functions from general-purpose methods - Groovy 3+ supports
::as an alternative syntax – both produce closures in Groovy
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 Composition and Chaining
Frequently Asked Questions
What is the difference between .& and :: in Groovy?
Both .& (method pointer) and :: (method reference) create closures from methods. The .& operator has been available since early Groovy versions and uses dynamic method dispatch. The :: operator was added in Groovy 3.0 for Java compatibility and follows Java-style resolution. In practice, both produce Closure objects and can be used interchangeably in most Groovy code.
Can I create a method pointer to an overloaded method in Groovy?
Yes. Groovy resolves overloaded methods dynamically at call time, not when the pointer is created. So a single method pointer like calc.&add can dispatch to different overloads depending on the arguments you pass. This is different from Java, where you must specify which overload to reference through a functional interface type.
What is a constructor reference in Groovy?
A constructor reference uses the .&new syntax (or ::new in Groovy 3+) to create a closure that calls a class constructor. For example, ArrayList.&new returns a closure that creates new ArrayList instances. The closure handles overloaded constructors through dynamic dispatch, so you can call it with different argument combinations.
Can I curry a method pointer in Groovy?
Absolutely. Since method pointers return standard Closure objects, you can use curry(), rcurry(), and ncurry() on them. This is a powerful pattern: create a method pointer to a general-purpose method, then curry it to produce specialized versions. For example, Math.&pow.rcurry(2) creates a ‘square’ function.
Do method pointers work with Groovy GDK extension methods?
Yes. Method pointers work with GDK extension methods (like tokenize, collect, padLeft, etc.) exactly the same way they work with methods defined directly on the class. Groovy’s .& operator captures whatever method is available at runtime, including methods added by the Groovy Development Kit.
Related Posts
Previous in Series: Groovy Curry and Partial Application
Next in Series: Groovy Closure Composition and Chaining
Related Topics You Might Like:
- Groovy Closures – The Complete Guide
- Groovy Closure Parameters, Delegate, and Owner
- Groovy Higher-Order Functions – collect, inject, groupBy
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment