Learn Groovy curry, rcurry, ncurry, and partial application with 10+ tested examples. Build reusable closures, loggers, validators, and pipelines on Groovy 5.x.
“Currying is the art of asking for less — turning a function that needs three ingredients into one that already has two.”
Haskell Curry, Mathematician
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate to Advanced | Reading Time: 16 minutes
Groovy curry lets you take a closure with multiple parameters and pre-fill some of them, producing a new closure that requires fewer arguments. If you already know about closures, delegate and owner, and higher-order functions like collect and inject, currying is the natural next step for building reusable, configurable closures.
Groovy curry lets you take a closure with multiple parameters and “pre-fill” some of them, producing a new closure that requires fewer arguments. This technique — formally called groovy partial application — is incredibly useful for creating reusable, configurable closures from general-purpose ones. Think of it as a closure factory: you start with a flexible closure and stamp out specialized versions by locking in specific arguments.
In this post, we will cover Groovy’s three currying methods (curry, rcurry, ncurry), walk through 10 tested examples with actual output, and explore real-world patterns where currying makes your code cleaner and more expressive.
Table of Contents
What is Currying?
Currying is named after mathematician Haskell Curry (who also gave his name to the Haskell programming language). In pure functional programming, currying means transforming a function that takes multiple arguments into a chain of functions that each take a single argument. For example, a function f(a, b, c) becomes f(a)(b)(c).
Groovy takes a slightly more practical approach. What Groovy calls “currying” is technically partial application — you fix one or more arguments of a closure and get back a new closure that takes the remaining arguments. The distinction matters to computer science purists, but for day-to-day programming, the result is the same: you get specialized closures from general ones.
According to the official Groovy documentation on currying, Groovy provides three methods for this purpose: curry(), rcurry(), and ncurry().
curry, rcurry, and ncurry at a Glance
Here is a quick summary of the three currying methods before we jump into examples:
| Method | What It Does | Fixes Arguments From |
|---|---|---|
curry(args...) | Fixes leftmost arguments | Left side (first, second, …) |
rcurry(args...) | Fixes rightmost arguments | Right side (…, second-to-last, last) |
ncurry(index, args...) | Fixes arguments starting at a specific index | Any position by index |
All three methods return a new closure. The original closure is never modified. You can curry a curried closure again to fix even more arguments — they compose naturally.
Curry Methods Quick Reference
// Original: 3 parameters
def greet = { greeting, title, name -> "${greeting}, ${title} ${name}!" }
// curry() fixes from the left
def hello = greet.curry("Hello") // fixes greeting="Hello"
println hello("Dr.", "Smith") // Hello, Dr. Smith!
// rcurry() fixes from the right
def forSmith = greet.rcurry("Smith") // fixes name="Smith"
println forSmith("Hi", "Mr.") // Hi, Mr. Smith!
// ncurry() fixes at a specific index
def withDr = greet.ncurry(1, "Dr.") // fixes title="Dr." at index 1
println withDr("Hey", "Jones") // Hey, Dr. Jones!
Output
Hello, Dr. Smith! Hi, Mr. Smith! Hey, Dr. Jones!
10 Practical Examples
Let us explore currying with real, practical code. Every example has been tested on Groovy 5.x with actual output shown.
Example 1: Basic curry() – Fix the Leftmost Arguments
The curry() method fixes arguments from the left side. It is the most commonly used currying method because we often want to specialize the first parameter of a general closure.
Example 1: Basic curry()
// A general-purpose math operation closure
def multiply = { a, b -> a * b }
// Create specialized versions by currying the first argument
def double_ = multiply.curry(2)
def triple = multiply.curry(3)
def tenTimes = multiply.curry(10)
println "double(5): ${double_(5)}"
println "triple(5): ${triple(5)}"
println "tenTimes(5): ${tenTimes(5)}"
// The original closure is unchanged
println "\nOriginal: ${multiply(7, 8)}"
// Currying multiple arguments at once
def add3 = { a, b, c -> a + b + c }
def add5and10 = add3.curry(5, 10)
println "\n5 + 10 + 20 = ${add5and10(20)}"
// The curried closure is a real closure -- you can pass it around
def numbers = [1, 2, 3, 4, 5]
println "Doubled list: ${numbers.collect(double_)}"
println "Tripled list: ${numbers.collect(triple)}"
// Type of a curried closure
println "\nType: ${double_.getClass().simpleName}"
println "Is a Closure: ${double_ instanceof Closure}"
Output
double(5): 10 triple(5): 15 tenTimes(5): 50 Original: 56 5 + 10 + 20 = 35 Doubled list: [2, 4, 6, 8, 10] Tripled list: [3, 6, 9, 12, 15] Type: CurriedClosure Is a Closure: true
Notice that curried closures are full Closure objects. You can pass them to collect, findAll, sort, or any other method that accepts closures. They just happen to have fewer required parameters than the original.
Example 2: rcurry() – Fix the Rightmost Arguments
The rcurry() method fixes arguments from the right side. It is useful when the last parameter is the one you want to specialize, while keeping the earlier parameters flexible.
Example 2: rcurry()
// A power function: base raised to exponent
def power = { base, exponent -> base ** exponent }
// Fix the exponent from the right
def square = power.rcurry(2)
def cube = power.rcurry(3)
def fourthPower = power.rcurry(4)
println "3 squared: ${square(3)}"
println "3 cubed: ${cube(3)}"
println "3 to 4th: ${fourthPower(3)}"
// A formatter closure: format a value with a suffix
def format = { value, suffix -> "${value}${suffix}" }
// Fix the suffix
def asPercent = format.rcurry("%")
def asDollars = format.rcurry(" USD")
def asKg = format.rcurry(" kg")
println "\nFormatted: ${asPercent(95)}"
println "Formatted: ${asDollars(42.50)}"
println "Formatted: ${asKg(75)}"
// Use curried closures in collection operations
def prices = [10.5, 20.0, 35.75, 8.99]
def formattedPrices = prices.collect(asDollars)
println "\nPrices: ${formattedPrices}"
// rcurry with multiple arguments
def describe = { adj, noun, verb -> "The ${adj} ${noun} ${verb}." }
def runsAway = describe.rcurry("runs away")
println "\n${runsAway('quick', 'fox')}"
println "${runsAway('lazy', 'dog')}"
Output
3 squared: 9 3 cubed: 27 3 to 4th: 81 Formatted: 95% Formatted: 42.50 USD Formatted: 75 kg Prices: [10.5 USD, 20.0 USD, 35.75 USD, 8.99 USD] The quick fox runs away. The lazy dog runs away.
The rcurry method is particularly handy for formatter closures and template patterns where the “shape” of the output is the last parameter.
Example 3: ncurry() – Fix Arguments at Any Position
When you need to fix a parameter in the middle of the argument list, ncurry() is your tool. The first argument is the zero-based index where the fixed values should be inserted.
Example 3: ncurry()
// A closure with three parameters
def sendMessage = { from, protocol, to ->
println "${from} --[${protocol}]--> ${to}"
}
// Fix the protocol (middle parameter, index 1)
def sendViaHttp = sendMessage.ncurry(1, "HTTP")
def sendViaGrpc = sendMessage.ncurry(1, "gRPC")
def sendViaMqtt = sendMessage.ncurry(1, "MQTT")
sendViaHttp("ClientA", "ServerX")
sendViaGrpc("ServiceB", "ServiceY")
sendViaMqtt("SensorC", "BrokerZ")
// Fix the middle parameter in a 4-param closure
def logEntry = { timestamp, level, source, message ->
"[${timestamp}] ${level} (${source}): ${message}"
}
// Fix the level at index 1
def infoLog = logEntry.ncurry(1, "INFO")
def errorLog = logEntry.ncurry(1, "ERROR")
println "\n${infoLog('2026-03-08', 'App', 'Server started')}"
println "${errorLog('2026-03-08', 'DB', 'Connection refused')}"
// ncurry with multiple values at an index
def table = { col1, col2, col3, col4 ->
"| ${col1} | ${col2} | ${col3} | ${col4} |"
}
// Fix columns 1 and 2 (index 1, two values)
def withMiddleFixed = table.ncurry(1, "FIXED-A", "FIXED-B")
println "\n${withMiddleFixed('Start', 'End')}"
println "${withMiddleFixed('Alpha', 'Omega')}"
Output
ClientA --[HTTP]--> ServerX ServiceB --[gRPC]--> ServiceY SensorC --[MQTT]--> BrokerZ [2026-03-08] INFO (App): Server started [2026-03-08] ERROR (DB): Connection refused | Start | FIXED-A | FIXED-B | End | | Alpha | FIXED-A | FIXED-B | Omega |
The ncurry method is the most flexible of the three. While curry and rcurry cover the majority of use cases, ncurry handles the edge case where the parameter you need to fix is somewhere in the middle.
Example 4: Chained Currying – Fixing Arguments Incrementally
You can curry a curried closure. Each call to curry() fixes one more argument, progressively reducing the number of required parameters until you have a zero-argument closure.
Example 4: Chained Currying
// Start with a 4-parameter closure
def sendEmail = { from, to, subject, body ->
"""\
|From: ${from}
|To: ${to}
|Subject: ${subject}
|Body: ${body}""".stripMargin()
}
// Step 1: Fix the sender
def fromAdmin = sendEmail.curry("admin@technoscripts.com")
// Step 2: Fix the recipient
def adminToUser = fromAdmin.curry("user@example.com")
// Step 3: Fix the subject
def welcomeEmail = adminToUser.curry("Welcome!")
// Step 4: Now only the body remains
println welcomeEmail("Thanks for signing up!")
println "\n---\n"
// You can use any of the intermediate curried closures
println fromAdmin("boss@company.com", "Report Ready", "Q1 report is attached.")
println "\n---\n"
println adminToUser("Password Reset", "Click the link to reset your password.")
// Check parameter counts at each stage
println "\n--- Parameter Counts ---"
println "Original: ${sendEmail.maximumNumberOfParameters}"
println "fromAdmin: ${fromAdmin.maximumNumberOfParameters}"
println "adminToUser: ${adminToUser.maximumNumberOfParameters}"
println "welcomeEmail: ${welcomeEmail.maximumNumberOfParameters}"
Output
From: admin@technoscripts.com To: user@example.com Subject: Welcome! Body: Thanks for signing up! --- From: admin@technoscripts.com To: boss@company.com Subject: Report Ready Body: Q1 report is attached. --- From: admin@technoscripts.com To: user@example.com Subject: Password Reset Body: Click the link to reset your password. --- Parameter Counts --- Original: 4 fromAdmin: 3 adminToUser: 2 welcomeEmail: 1
Each currying step produces a new closure with one fewer required parameter. You can use the maximumNumberOfParameters property to check how many arguments a closure still needs. This is useful for writing generic code that adapts to closures of different arities.
Example 5: Building a Configurable Logger with Curry
One of the most practical uses of currying is building configurable utility closures. Here is a logging system where you create specialized loggers by currying a general-purpose log closure.
Example 5: Configurable Logger
// General-purpose log closure
def log = { String level, String component, String message ->
def timestamp = new Date().format("yyyy-MM-dd HH:mm:ss")
println "[${timestamp}] ${level.padRight(5)} [${component}] ${message}"
}
// Create level-specific loggers
def info = log.curry("INFO")
def warn = log.curry("WARN")
def error = log.curry("ERROR")
def debug = log.curry("DEBUG")
// Create component-specific loggers
def dbInfo = info.curry("Database")
def dbError = error.curry("Database")
def apiInfo = info.curry("API")
def apiWarn = warn.curry("API")
// Use them -- clean and descriptive
dbInfo("Connection pool initialized with 10 connections")
dbInfo("Query executed in 42ms")
dbError("Failed to connect to replica")
apiInfo("Endpoint /users registered")
apiWarn("Rate limit approaching: 950/1000 requests")
println "\n--- Direct use of level loggers ---"
info("Scheduler", "Cron job started")
error("Auth", "Invalid token received")
debug("Cache", "Cache miss for key: user_42")
Output
[2026-03-09 16:20:11] INFO [Database] Connection pool initialized with 10 connections [2026-03-09 16:20:11] INFO [Database] Query executed in 42ms [2026-03-09 16:20:11] ERROR [Database] Failed to connect to replica [2026-03-09 16:20:11] INFO [API] Endpoint /users registered [2026-03-09 16:20:11] WARN [API] Rate limit approaching: 950/1000 requests --- Direct use of level loggers --- [2026-03-
This is a clean alternative to creating separate logger classes or using complex configuration files. You define the logging logic once and stamp out specialized versions with curry(). Each specialized logger is a simple closure that takes just one string argument — the message.
Example 6: Creating Validators with Curry
Currying is excellent for building a library of validation closures from a few generic ones.
Example 6: Validators with Curry
// Generic range validator
def inRange = { min, max, value -> value >= min && value <= max }
// Create specific validators
def isValidAge = inRange.curry(0, 150)
def isValidPercent = inRange.curry(0, 100)
def isValidPort = inRange.curry(1, 65535)
def isValidRgb = inRange.curry(0, 255)
println "Age 25 valid: ${isValidAge(25)}"
println "Age -5 valid: ${isValidAge(-5)}"
println "Percent 101: ${isValidPercent(101)}"
println "Port 8080: ${isValidPort(8080)}"
println "RGB 200: ${isValidRgb(200)}"
println "RGB 300: ${isValidRgb(300)}"
// Generic length validator
def hasLength = { min, max, String str ->
str.length() >= min && str.length() <= max
}
def isValidUsername = hasLength.curry(3, 20)
def isValidPassword = hasLength.curry(8, 128)
def isValidTweet = hasLength.curry(1, 280)
println "\n--- String Validators ---"
println "Username 'ab': ${isValidUsername('ab')}"
println "Username 'alice': ${isValidUsername('alice')}"
println "Password '1234567': ${isValidPassword('1234567')}"
println "Password '12345678': ${isValidPassword('12345678')}"
// Combine validators
def validateUser = { String name, int age ->
def errors = []
if (!isValidUsername(name)) errors << "Invalid username"
if (!isValidAge(age)) errors << "Invalid age"
errors.isEmpty() ? "Valid!" : "Errors: ${errors.join(', ')}"
}
println "\n--- Combined Validation ---"
println "alice, 25: ${validateUser('alice', 25)}"
println "ab, 25: ${validateUser('ab', 25)}"
println "alice, -1: ${validateUser('alice', -1)}"
println "ab, 200: ${validateUser('ab', 200)}"
Output
Age 25 valid: true Age -5 valid: false Percent 101: false Port 8080: true RGB 200: true RGB 300: false --- String Validators --- Username 'ab': false Username 'alice': true Password '1234567': false Password '12345678': true --- Combined Validation --- alice, 25: Valid! ab, 25: Errors: Invalid username alice, -1: Errors: Invalid age ab, 200: Errors: Invalid username, Invalid age
Instead of writing separate validation methods for each data type, you write one generic validator and curry it into specific ones. This is the DRY principle applied through functional programming.
Example 7: Currying with Collection Operations
Curried closures work well with Groovy’s collection methods. You can pass them directly to collect, findAll, sort, and any other method that accepts a closure.
Example 7: Currying with Collections
// Multiplier closure
def multiply = { factor, value -> factor * value }
// Create specialized transformers
def double_ = multiply.curry(2)
def triple = multiply.curry(3)
def halve = multiply.curry(0.5)
def numbers = [10, 20, 30, 40, 50]
println "Original: ${numbers}"
println "Doubled: ${numbers.collect(double_)}"
println "Tripled: ${numbers.collect(triple)}"
println "Halved: ${numbers.collect(halve)}"
// Filter with curried closures
def greaterThan = { threshold, value -> value > threshold }
def lessThan = { threshold, value -> value < threshold }
def gt25 = greaterThan.curry(25)
def lt45 = lessThan.curry(45)
println "\n> 25: ${numbers.findAll(gt25)}"
println "< 45: ${numbers.findAll(lt45)}"
println "> 25 AND < 45: ${numbers.findAll(gt25).findAll(lt45)}"
// String transformations with curry
def wrap = { String before, String after, String content ->
"${before}${content}${after}"
}
def inParens = wrap.curry("(").rcurry(")")
def inBrackets = wrap.curry("[").rcurry("]")
def bold = wrap.curry("<b>").rcurry("</b>")
def words = ["hello", "world", "groovy"]
println "\nIn parens: ${words.collect(inParens)}"
println "In brackets: ${words.collect(inBrackets)}"
println "Bold: ${words.collect(bold)}"
Output
Original: [10, 20, 30, 40, 50] Doubled: [20, 40, 60, 80, 100] Tripled: [30, 60, 90, 120, 150] Halved: [5.0, 10.0, 15.0, 20.0, 25.0] > 25: [30, 40, 50] < 45: [10, 20, 30, 40] > 25 AND < 45: [30, 40] In parens: [()hello, ()world, ()groovy] In brackets: [[]hello, []world, []groovy] Bold: [hello, world, groovy]
This example shows the beautiful interplay between currying and collection operations. You define general-purpose closures once and then curry them into specific versions that plug directly into collect, findAll, and other higher-order functions. No lambda boilerplate, no anonymous classes.
Example 8: Closure Composition with Curry
Groovy closures support composition with the << (left-shift) and >> (right-shift) operators. When combined with currying, you can build powerful processing pipelines.
Example 8: Composition with Curry
// Basic closure composition
def add = { a, b -> a + b }
def multiply = { a, b -> a * b }
def negate = { x -> -x }
def double_ = { x -> x * 2 }
// Compose: first double, then negate
def doubleAndNegate = negate << double_
println "doubleAndNegate(5): ${doubleAndNegate(5)}" // -10
// Using >> (reverse composition): double then negate
def sameResult = double_ >> negate
println "sameResult(5): ${sameResult(5)}" // -10
// Combining curry with composition
def addTen = add.curry(10)
def multiplyByThree = multiply.curry(3)
// Pipeline: add 10, then multiply by 3
def addThenMultiply = multiplyByThree << addTen
println "\naddThenMultiply(5): ${addThenMultiply(5)}" // (5+10)*3 = 45
// Reverse: multiply by 3, then add 10
def multiplyThenAdd = addTen << multiplyByThree
println "multiplyThenAdd(5): ${multiplyThenAdd(5)}" // (5*3)+10 = 25
// Build a text processing pipeline
def trim = { String s -> s.strip() }
def lower = { String s -> s.toLowerCase() }
def removeSpaces = { String s -> s.replaceAll(/\s+/, '-') }
def addPrefix = { String prefix, String s -> "${prefix}${s}" }
def slugify = (addPrefix.curry("/")) << removeSpaces << lower << trim
println "\nSlugify examples:"
println slugify(" Hello World ")
println slugify(" Groovy Curry Tutorial ")
println slugify(" My Blog Post Title ")
Output
doubleAndNegate(5): -10 sameResult(5): -10 addThenMultiply(5): 45 multiplyThenAdd(5): 25 Slugify examples: /hello-world /groovy-curry-tutorial /my-blog-post-title
The << operator composes right-to-left (like mathematical function composition: f << g means f(g(x))). The >> operator composes left-to-right (pipeline style: f >> g means g(f(x))). Combined with curry, you can build reusable processing pipelines from small, composable pieces.
Example 9: Curried Event Handlers and Callbacks
Currying is perfect for creating event handler factories where you want to bind some context at registration time and receive event data at dispatch time.
Example 9: Event Handlers
// Simple event system
class EventBus {
Map<String, List<Closure>> handlers = [:].withDefault { [] }
void on(String event, Closure handler) {
handlers[event] << handler
}
void emit(String event, Map data = [:]) {
handlers[event].each { handler -> handler(data) }
}
}
// General-purpose handler closure
def handleEvent = { String component, String action, Map data ->
println "[${component}] ${action}: ${data}"
}
// Create curried handlers for different components
def userHandler = handleEvent.curry("UserService")
def orderHandler = handleEvent.curry("OrderService")
// Further specialize with specific actions
def onUserCreated = userHandler.curry("created")
def onUserDeleted = userHandler.curry("deleted")
def onOrderPlaced = orderHandler.curry("placed")
def onOrderShipped = orderHandler.curry("shipped")
// Register handlers
def bus = new EventBus()
bus.on("user.created", onUserCreated)
bus.on("user.deleted", onUserDeleted)
bus.on("order.placed", onOrderPlaced)
bus.on("order.shipped", onOrderShipped)
// Emit events
bus.emit("user.created", [id: 42, name: "Alice"])
bus.emit("order.placed", [orderId: "ORD-001", total: 99.99])
bus.emit("user.deleted", [id: 42, reason: "requested"])
bus.emit("order.shipped", [orderId: "ORD-001", carrier: "FedEx"])
Output
[UserService] created: [id:42, name:Alice] [OrderService] placed: [orderId:ORD-001, total:99.99] [UserService] deleted: [id:42, reason:requested] [OrderService] shipped: [orderId:ORD-001, carrier:FedEx]
The beauty here is that one general-purpose handler closure generates all your specialized handlers. Adding a new component or action is just another curry() call — no new classes, no inheritance, no boilerplate.
Example 10: Combining Curry with Memoization
Groovy closures support memoize() for caching results. You can combine memoization with currying to create cached specialized closures.
Example 10: Curry + Memoize
// An "expensive" computation
def compute = { String operation, int x, int y ->
println " Computing ${operation}(${x}, ${y})..."
Thread.sleep(100) // Simulate expensive work
switch (operation) {
case "add": return x + y
case "mul": return x * y
case "pow": return x ** y
default: return 0
}
}
// Create a memoized version first, then curry
def cachedCompute = compute.memoize()
def cachedAdd = cachedCompute.curry("add")
def cachedMul = cachedCompute.curry("mul")
def cachedPow = cachedCompute.curry("pow")
println "First calls (will compute):"
println "add(3, 4) = ${cachedAdd(3, 4)}"
println "mul(3, 4) = ${cachedMul(3, 4)}"
println "pow(2, 10) = ${cachedPow(2, 10)}"
println "\nSecond calls (cached - no 'Computing...' message):"
println "add(3, 4) = ${cachedAdd(3, 4)}"
println "mul(3, 4) = ${cachedMul(3, 4)}"
println "pow(2, 10) = ${cachedPow(2, 10)}"
println "\nNew arguments (will compute):"
println "add(5, 6) = ${cachedAdd(5, 6)}"
// Useful pattern: memoized fibonacci with curry
def fib
fib = { BigInteger n ->
n <= 1 ? n : fib(n - 1) + fib(n - 2)
}.memoize()
println "\nFibonacci(30): ${fib(30)}"
println "Fibonacci(50): ${fib(50)}"
Output
First calls (will compute): Computing add(3, 4)... add(3, 4) = 7 Computing mul(3, 4)... mul(3, 4) = 12 Computing pow(2, 10)... pow(2, 10) = 1024 Second calls (cached - no 'Computing...' message): add(3, 4) = 7 mul(3, 4) = 12 pow(2, 10) = 1024 New arguments (will compute): Computing add(5, 6)... add(5, 6) = 11 Fibonacci(30): 832040 Fibonacci(50): 12586269025
The order matters: apply memoize() first, then curry(). This way, the cache is shared across all curried variants. If you curry first and then memoize, each curried closure gets its own separate cache.
Currying vs Partial Application
Although Groovy uses the name “curry,” what it actually implements is partial application. Here is the technical difference:
| Concept | True Currying | Partial Application (Groovy) |
|---|---|---|
| What it does | Transforms f(a,b,c) into f(a)(b)(c) | Fixes some arguments: f(a,b,c) -> g(c) where a,b are fixed |
| Result | Chain of single-argument functions | A function with fewer arguments |
| Can fix multiple args at once? | No, always one at a time | Yes, curry(a, b) fixes two at once |
| Can fix args from the right? | No | Yes, via rcurry() |
| Languages | Haskell, OCaml (automatically) | Groovy, Scala, JavaScript (manually) |
For practical purposes, the distinction rarely matters. Groovy’s curry() does what you expect: it fixes arguments and gives you back a simpler closure. The official Groovy documentation uses the term “currying” for this feature, so we will too.
Real-World Patterns
Here are the most common patterns where currying adds real value in production Groovy code:
Pattern 1: Configuration Factories
Configuration Factory Pattern
// Database query builder
def query = { String table, String condition, String orderBy ->
"SELECT * FROM ${table} WHERE ${condition} ORDER BY ${orderBy}"
}
def userQuery = query.curry("users")
def activeUsers = userQuery.curry("active = true")
println activeUsers("last_login DESC")
println activeUsers("name ASC")
println userQuery("age > 18", "name")
Output
SELECT * FROM users WHERE active = true ORDER BY last_login DESC SELECT * FROM users WHERE active = true ORDER BY name ASC SELECT * FROM users WHERE age > 18 ORDER BY name
Pattern 2: Unit Converters
Unit Converter Pattern
def convert = { double factor, double offset, double value ->
value * factor + offset
}
def celsiusToFahrenheit = convert.curry(9.0/5.0, 32)
def kmToMiles = convert.curry(0.621371, 0)
def kgToLbs = convert.curry(2.20462, 0)
println "100C = ${celsiusToFahrenheit(100)}F"
println "42km = ${String.format('%.2f', kmToMiles(42))} miles"
println "80kg = ${String.format('%.2f', kgToLbs(80))} lbs"
// Apply to lists
def tempsC = [0, 20, 37, 100]
def tempsF = tempsC.collect(celsiusToFahrenheit)
println "\nCelsius: ${tempsC}"
println "Fahrenheit: ${tempsF}"
Output
100C = 212.0F 42km = 26.10 miles 80kg = 176.37 lbs Celsius: [0, 20, 37, 100] Fahrenheit: [32.0, 68.0, 98.60000000000001, 212.0]
Pattern 3: Retry Logic
Retry Pattern
def withRetry = { int maxRetries, long delayMs, Closure action ->
int attempts = 0
while (attempts < maxRetries) {
try {
return action()
} catch (Exception e) {
attempts++
if (attempts >= maxRetries) throw e
println " Attempt ${attempts} failed: ${e.message}. Retrying in ${delayMs}ms..."
Thread.sleep(delayMs)
}
}
}
// Create specialized retry policies
def quickRetry = withRetry.curry(3, 100) // 3 retries, 100ms delay
def patientRetry = withRetry.curry(5, 1000) // 5 retries, 1s delay
// Simulate a flaky operation
int callCount = 0
def flakyOperation = {
callCount++
if (callCount < 3) throw new RuntimeException("Server busy")
return "Success on attempt ${callCount}!"
}
println quickRetry(flakyOperation)
Output
Attempt 1 failed: Server busy. Retrying in 100ms... Attempt 2 failed: Server busy. Retrying in 100ms... Success on attempt 3!
Common Pitfalls
Watch out for these common mistakes when using currying in Groovy:
- Currying too many arguments — if you curry more arguments than the closure has parameters, you will get a runtime error. The closure’s
maximumNumberOfParametersproperty helps you check. - Wrong order with memoize — always call
memoize()beforecurry()if you want all curried versions to share the same cache. - Confusing curry and rcurry —
curryfills from the left,rcurryfills from the right. If your arguments end up in wrong positions, you probably used the wrong method. - Losing type information — curried closures lose the parameter type metadata from the original closure. If you rely on
@CompileStatic, be aware that type checking is weaker for curried closures. - Over-currying — not everything needs to be curried. If you find yourself currying a closure just to immediately call it, you are adding complexity without benefit. Curry when you need to reuse the specialized version multiple times.
Conclusion
We have covered the full spectrum of Groovy curry and partial application. From the basic curry() that fixes leftmost arguments, through rcurry() for rightmost arguments, to ncurry() for positional fixing — you now have the tools to create specialized closures from general-purpose ones without writing redundant code.
Currying is at its best when combined with other functional features: composition operators (<< and >>), memoization, and higher-order collection methods. Together, they let you write Groovy code that is concise, composable, and easy to test.
Summary
curry()fixes arguments from the left — the most common currying methodrcurry()fixes arguments from the right — useful for formatters and suffixesncurry(index, args)fixes arguments at any position by zero-based index- Curried closures are real
Closureobjects — pass them tocollect,findAll, and any closure-accepting method - Chain
curry()calls to fix arguments incrementally - Combine with
memoize()for cached specialized closures (memoize first, then curry) - Use composition operators (
<<and>>) with curried closures to build processing pipelines - Groovy’s “currying” is technically partial application, but the practical effect is the same
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 Method References and Method Pointers
Frequently Asked Questions
What is curry() in Groovy?
curry() is a method on Groovy closures that performs partial application. It fixes one or more leftmost arguments of a closure and returns a new closure that accepts the remaining arguments. For example, if you have def add = { a, b -> a + b }, then add.curry(10) returns a new closure that only needs one argument and always adds 10 to it. The original closure is not modified.
What is the difference between curry, rcurry, and ncurry in Groovy?
curry() fixes arguments from the left (first parameter onward). rcurry() fixes arguments from the right (last parameter backward). ncurry() fixes arguments at a specific zero-based index position. For example, with a closure { a, b, c -> … }: curry(x) fixes a=x, rcurry(x) fixes c=x, and ncurry(1, x) fixes b=x. All three return a new closure with fewer required parameters.
Is Groovy curry the same as currying in Haskell?
Not exactly. In Haskell, currying transforms a multi-argument function into a chain of single-argument functions automatically. Groovy’s curry() is technically partial application — it fixes some arguments and returns a function that takes the rest. However, the Groovy documentation and community use the term ‘currying’ for this feature. The practical result is similar: you get specialized functions from general ones.
Can I curry a curried closure in Groovy?
Yes, you can chain curry calls. Each call fixes more arguments and returns a new closure with fewer required parameters. For example: def f = { a, b, c -> a + b + c }; def g = f.curry(1); def h = g.curry(2); println h(3) prints 6. You can check the remaining parameter count with closure.maximumNumberOfParameters.
How do I combine curry with memoize in Groovy?
Call memoize() first, then curry(). This way all curried versions share the same cache. Example: def cached = expensiveOp.memoize(); def specialized = cached.curry('param1'). If you curry first and then memoize, each curried closure gets its own independent cache, which is usually not what you want. The memoize() method caches results based on all arguments including the curried ones.
Related Posts
Previous in Series: Groovy Higher-Order Functions – collect, inject, groupBy
Next in Series: Groovy Method References and Method Pointers
Related Topics You Might Like:
- Groovy Closures – The Complete Guide
- Groovy Closure Parameters – it, delegate, owner
- Groovy Higher-Order Functions – collect, inject, groupBy
Happy currying with Groovy!
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment