Groovy idiomatic patterns that have no Java equivalent. 12 tested examples covering Category, @Delegate, closure-based DSLs, builder patterns, method chaining, and patterns that only work because Groovy is Groovy.
“Design patterns are not about memorizing class diagrams – they’re about recognizing recurring problems. In Groovy, half the GoF patterns collapse into a closure and a map.”
Gang of Four, Design Patterns
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate | Reading Time: 20 minutes
Our Groovy Design Patterns post covers classic GoF patterns – Singleton, Strategy, Observer – implemented in Groovy. This post goes further. These are groovy idiomatic patterns that do not exist in the Gang of Four book because they only make sense in a language with closures, categories, dynamic dispatch, and @Delegate. Patterns like the Category pattern, closure-based DSL builders, method-missing handlers, and delegation chains are uniquely Groovy.
Where the classic patterns post showed how Groovy simplifies Java patterns, this post shows patterns that Java cannot express at all. You will learn how to extend third-party classes at runtime with Categories, compose behavior with @Delegate chains, build fluent DSLs using closure delegation strategies, implement the Loan pattern for resource management, and use methodMissing for dynamic proxy objects. Every example is tested and shows real output.
If you have read the classic patterns post and want to write code that feels truly Groovy rather than “Java with nicer syntax,” this is the next step.
Table of Contents
Quick Reference Table
| Pattern | Java Approach | Groovy Approach | Key Feature |
|---|---|---|---|
| Strategy | Interface + implementations | Closures / Map of closures | First-class functions |
| Observer | Listener interface + list | Closure list / @Observable | Dynamic dispatch |
| Decorator | Wrapper classes | @Delegate annotation | Compile-time delegation |
| Delegation | Manual forwarding | @Delegate | AST transformation |
| Builder | Static inner class | @Builder or native with closures | AST + named params |
| Singleton | Double-checked locking | @Singleton | Thread-safe by default |
| Category (Pimp My Library) | Utility classes | use(Category) | Scoped extension methods |
| Chain of Responsibility | Handler interface + chain | Closure composition | Functional chaining |
Examples
Example 1: Strategy Pattern with Closures
What we’re doing: Replacing an entire interface hierarchy with closures to swap sorting strategies at runtime.
Example 1: Strategy Pattern with Closures
// --- Java-style Strategy (verbose) ---
interface SortStrategy {
List sort(List items)
}
class BubbleSort implements SortStrategy {
List sort(List items) { items.sort() }
}
class ReverseSort implements SortStrategy {
List sort(List items) { items.sort().reverse() }
}
class JavaStyleSorter {
SortStrategy strategy
List execute(List items) { strategy.sort(items) }
}
def javaSorter = new JavaStyleSorter(strategy: new ReverseSort())
println "Java-style: ${javaSorter.execute([3, 1, 4, 1, 5])}"
// --- Groovy-style Strategy (closures) ---
def strategies = [
ascending : { items -> items.sort() },
descending: { items -> items.sort().reverse() },
byLength : { items -> items.sort { it.toString().size() } },
shuffled : { items -> items.shuffled() }
]
class GroovySorter {
Closure strategy
List execute(List items) { strategy(items.collect()) }
}
def sorter = new GroovySorter(strategy: strategies.ascending)
println "Ascending: ${sorter.execute([3, 1, 4, 1, 5])}"
sorter.strategy = strategies.descending
println "Descending: ${sorter.execute([3, 1, 4, 1, 5])}"
sorter.strategy = strategies.byLength
println "By length: ${sorter.execute(['fig', 'apple', 'kiwi', 'date'])}"
// Strategies can also be inline lambdas
sorter.strategy = { it.sort { a, b -> a % 3 <=> b % 3 } }
println "Custom mod3: ${sorter.execute([9, 7, 3, 5, 6, 2])}"
Output
Java-style: [5, 4, 3, 1, 1] Ascending: [1, 1, 3, 4, 5] Descending: [5, 4, 3, 1, 1] By length: [fig, kiwi, date, apple] Custom mod3: [9, 3, 6, 7, 5, 2]
What happened here: The Java version required an interface, two implementation classes, and a context class – all to swap one line of logic. The Groovy version stores strategies as closures in a map. Swapping strategy is just reassigning a closure reference. You can even define strategies inline as lambdas without creating a class. This is the core Groovy insight: closures are single-method interfaces, and most behavioral patterns are just swappable single-method interfaces.
Example 2: Strategy Pattern – Pricing Engine
What we’re doing: Building a real-world pricing engine where discount strategies are selected dynamically based on customer type.
Example 2: Strategy Pattern – Pricing Engine
// Discount strategies as a map of closures
def discountStrategies = [
regular : { price, qty -> price * qty },
premium : { price, qty -> price * qty * 0.90 }, // 10% off
vip : { price, qty -> price * qty * 0.80 }, // 20% off
bulk : { price, qty -> qty >= 100 ? price * qty * 0.70 : price * qty },
employee: { price, qty -> price * qty * 0.50 } // 50% off
]
class PricingEngine {
Map<String, Closure> strategies
BigDecimal calculate(String customerType, BigDecimal unitPrice, int quantity) {
def strategy = strategies[customerType] ?: strategies['regular']
def total = strategy(unitPrice, quantity) as BigDecimal
return total.setScale(2, BigDecimal.ROUND_HALF_UP)
}
}
def engine = new PricingEngine(strategies: discountStrategies)
// Test various customer types
def orders = [
[type: 'regular', price: 29.99, qty: 5],
[type: 'premium', price: 29.99, qty: 5],
[type: 'vip', price: 29.99, qty: 5],
[type: 'bulk', price: 29.99, qty: 150],
[type: 'employee', price: 29.99, qty: 2],
[type: 'unknown', price: 29.99, qty: 3] // Falls back to regular
]
orders.each { order ->
def total = engine.calculate(order.type, order.price, order.qty)
println "${order.type.padRight(10)} | ${order.qty} units @ \$${order.price} = \$${total}"
}
Output
regular | 5 units @ $29.99 = $149.95 premium | 5 units @ $29.99 = $134.96 vip | 5 units @ $29.99 = $119.96 bulk | 150 units @ $29.99 = $3148.95 employee | 2 units @ $29.99 = $29.99 unknown | 3 units @ $29.99 = $89.97
What happened here: The pricing engine selects a strategy from a map using the customer type as the key. The ?: strategies['regular'] provides a fallback for unknown types. Adding a new customer tier is a single line in the map – no new class, no interface change, no factory method. This pattern is extremely common in business applications where rules change frequently. In Java you would need a DiscountStrategy interface, five implementation classes, and a factory to select them.
Example 3: Observer Pattern
What we’re doing: Implementing the Observer pattern using a list of closures as listeners, with no interface boilerplate.
Example 3: Observer Pattern
class EventEmitter {
private Map<String, List<Closure>> listeners = [:].withDefault { [] }
void on(String event, Closure handler) {
listeners[event] << handler
}
void off(String event, Closure handler) {
listeners[event].remove(handler)
}
void emit(String event, Map data = [:]) {
listeners[event].each { it(data) }
}
}
// Create an order system with event-driven architecture
def emitter = new EventEmitter()
// Register observers (listeners)
emitter.on('order.created') { data ->
println "[Inventory] Reserving ${data.quantity} units of ${data.product}"
}
emitter.on('order.created') { data ->
println "[Email] Sending confirmation to ${data.customer}"
}
emitter.on('order.created') { data ->
def total = data.price * data.quantity
println "[Billing] Charging \$${total} to ${data.customer}"
}
emitter.on('order.shipped') { data ->
println "[Tracking] Order ${data.orderId} shipped via ${data.carrier}"
}
emitter.on('order.shipped') { data ->
println "[Email] Shipping notification sent to ${data.customer}"
}
// Fire events
println "--- Order Created ---"
emitter.emit('order.created', [
product: 'Mechanical Keyboard', quantity: 2,
price: 89.99, customer: 'nirranjan@example.com'
])
println "\n--- Order Shipped ---"
emitter.emit('order.shipped', [
orderId: 'ORD-1042', carrier: 'FedEx',
customer: 'nirranjan@example.com'
])
Output
--- Order Created --- [Inventory] Reserving 2 units of Mechanical Keyboard [Email] Sending confirmation to nirranjan@example.com [Billing] Charging $179.98 to nirranjan@example.com --- Order Shipped --- [Tracking] Order ORD-1042 shipped via FedEx [Email] Shipping notification sent to nirranjan@example.com
What happened here: The EventEmitter stores listeners as a map of event names to closure lists. Registering an observer is just appending a closure with <<. Emitting an event iterates the closure list and calls each one with the event data. There is no EventListener interface, no PropertyChangeSupport, no anonymous inner class. The entire pattern is 15 lines. The map-of-lists approach also gives us named events for free – you can observe 'order.created' independently from 'order.shipped'.
Example 4: Observer Pattern with @Observable
What we’re doing: Using Groovy’s built-in @Observable AST transformation to get JavaBeans-style property change notifications automatically.
Example 4: Observer Pattern with @Observable
import groovy.beans.Bindable
class UserProfile {
@Bindable String name
@Bindable String email
@Bindable int age
}
def profile = new UserProfile(name: 'Nirranjan', email: 'nirranjan@old.com', age: 28)
// Register property change listeners
profile.addPropertyChangeListener('name') { evt ->
println "[Name Changed] '${evt.oldValue}' -> '${evt.newValue}'"
}
profile.addPropertyChangeListener('email') { evt ->
println "[Email Changed] '${evt.oldValue}' -> '${evt.newValue}'"
}
// Global listener for any property
profile.addPropertyChangeListener { evt ->
println "[Audit Log] ${evt.propertyName}: ${evt.oldValue} -> ${evt.newValue}"
}
// Trigger changes
profile.name = 'Nirranjan Smith'
profile.email = 'nirranjan@newdomain.com'
profile.age = 29
Output
[Name Changed] 'Nirranjan' -> 'Nirranjan Smith' [Audit Log] name: Nirranjan -> Nirranjan Smith [Email Changed] 'nirranjan@old.com' -> 'nirranjan@newdomain.com' [Audit Log] email: nirranjan@old.com -> nirranjan@newdomain.com [Audit Log] age: 28 -> 29
What happened here: The @Bindable annotation (from groovy.beans) triggers an AST transformation that adds PropertyChangeSupport to the class and generates setter methods that fire change events. You get per-property and global listeners without writing any plumbing code. The global listener catches the age change even though we did not register a specific listener for it. This is how Groovy’s Swing Builder implements data binding under the hood.
Example 5: Decorator Pattern with @Delegate
What we’re doing: Using @Delegate to implement the Decorator pattern without manually forwarding dozens of methods.
Example 5: Decorator Pattern with @Delegate
interface Coffee {
String getDescription()
BigDecimal getCost()
}
class SimpleCoffee implements Coffee {
String getDescription() { 'Simple Coffee' }
BigDecimal getCost() { 2.00 }
}
// Decorators using @Delegate
class MilkDecorator implements Coffee {
@Delegate Coffee base
String getDescription() { "${base.description}, Milk" }
BigDecimal getCost() { base.cost + 0.50 }
}
class SugarDecorator implements Coffee {
@Delegate Coffee base
String getDescription() { "${base.description}, Sugar" }
BigDecimal getCost() { base.cost + 0.25 }
}
class WhipDecorator implements Coffee {
@Delegate Coffee base
String getDescription() { "${base.description}, Whipped Cream" }
BigDecimal getCost() { base.cost + 0.75 }
}
// Stack decorators
def coffee = new SimpleCoffee()
println "${coffee.description} = \$${coffee.cost}"
coffee = new MilkDecorator(base: coffee)
println "${coffee.description} = \$${coffee.cost}"
coffee = new SugarDecorator(base: coffee)
println "${coffee.description} = \$${coffee.cost}"
coffee = new WhipDecorator(base: coffee)
println "${coffee.description} = \$${coffee.cost}"
println "\n--- One-liner chain ---"
def fancy = new WhipDecorator(base:
new SugarDecorator(base:
new MilkDecorator(base:
new SimpleCoffee())))
println "${fancy.description} = \$${fancy.cost}"
Output
Simple Coffee = $2.00 Simple Coffee, Milk = $2.50 Simple Coffee, Milk, Sugar = $2.75 Simple Coffee, Milk, Sugar, Whipped Cream = $3.50 --- One-liner chain --- Simple Coffee, Milk, Sugar, Whipped Cream = $3.50
What happened here: @Delegate is an AST transformation that automatically forwards all methods from the delegate to the decorated object. Without it, each decorator would need to manually implement every method of the Coffee interface just to forward to the base. With @Delegate, we only override the methods we want to change (getDescription and getCost) and everything else passes through automatically. The pattern stacks naturally – each decorator wraps the previous one.
Example 6: Delegation Pattern with @Delegate
What we’re doing: Using @Delegate for composition-over-inheritance – delegating to multiple objects and controlling method conflicts.
Example 6: Delegation Pattern with @Delegate
class Engine {
int horsepower
String type
String start() { "Engine started: ${horsepower}hp ${type}" }
String stop() { "Engine stopped" }
String rev() { "Vroooom! (${horsepower}hp)" }
}
class GPS {
String destination
String navigate(String dest) {
this.destination = dest
"Navigating to ${dest}"
}
String currentLocation() { "Current location: 40.7128 N, 74.0060 W" }
String stop() { "GPS navigation stopped" } // Conflicts with Engine.stop()
}
class AudioSystem {
int volume = 50
String play(String song) { "Playing: ${song} at volume ${volume}" }
String setVolume(int v) { this.volume = v; "Volume set to ${v}" }
}
// Car delegates to multiple components
class Car {
@Delegate Engine engine
@Delegate(methodAnnotations = true) GPS gps
@Delegate AudioSystem audio
// When stop() conflicts, the first @Delegate wins (Engine)
// Override explicitly if needed
String stopAll() {
"${engine.stop()} | ${gps.stop()}"
}
}
def car = new Car(
engine: new Engine(horsepower: 300, type: 'V6'),
gps: new GPS(),
audio: new AudioSystem()
)
// All delegated methods are available directly on Car
println car.start()
println car.rev()
println car.navigate('San Francisco')
println car.currentLocation()
println car.play('Highway Star')
println car.setVolume(80)
println car.play('Bohemian Rhapsody')
println "\n--- Conflict resolution ---"
println "car.stop() goes to Engine: ${car.stop()}"
println "car.stopAll(): ${car.stopAll()}"
Output
Engine started: 300hp V6 Vroooom! (300hp) Navigating to San Francisco Current location: 40.7128 N, 74.0060 W Playing: Highway Star at volume 50 Volume set to 80 Playing: Bohemian Rhapsody at volume 80 --- Conflict resolution --- car.stop() goes to Engine: Engine stopped car.stopAll(): Engine stopped | GPS navigation stopped
What happened here: The Car class delegates to three components without extending any of them. @Delegate generates forwarding methods at compile time – car.start() calls engine.start(), car.navigate() calls gps.navigate(), and so on. When two delegates have a method with the same name (both Engine and GPS have stop()), the first declared @Delegate wins. For explicit control, write your own method like stopAll(). This is true composition-over-inheritance without any manual forwarding.
Example 7: Builder Pattern – Native Groovy vs @Builder
What we’re doing: Comparing three approaches to the Builder pattern: a Groovy closure-based builder, the @Builder AST transformation, and named parameters.
Example 7: Builder Pattern – Native Groovy vs @Builder
import groovy.transform.*
// --- Approach 1: Named parameters (simplest) ---
@ToString(includeNames = true)
class ServerConfig {
String host = 'localhost'
int port = 8080
boolean ssl = false
int maxConnections = 100
String logLevel = 'INFO'
}
def config1 = new ServerConfig(host: 'api.example.com', port: 443, ssl: true)
println "Named params: ${config1}"
// --- Approach 2: @Builder annotation ---
@Builder
@ToString(includeNames = true)
class DatabaseConfig {
String url
String username
String password
int poolSize = 10
int timeout = 30000
boolean autoCommit = true
}
def config2 = DatabaseConfig.builder()
.url('jdbc:postgresql://db.example.com:5432/mydb')
.username('admin')
.password('s3cret')
.poolSize(25)
.timeout(60000)
.build()
println "Builder: ${config2}"
// --- Approach 3: Closure-based DSL builder ---
class AppConfigBuilder {
String appName
String version
Map<String, String> env = [:]
List<String> features = []
void environment(Map<String, String> vars) { env.putAll(vars) }
void feature(String name) { features << name }
static AppConfigBuilder build(@DelegatesTo(AppConfigBuilder) Closure spec) {
def builder = new AppConfigBuilder()
spec.delegate = builder
spec.resolveStrategy = Closure.DELEGATE_FIRST
spec()
return builder
}
String toString() { "App[${appName} v${version}, features=${features}, env=${env}]" }
}
def config3 = AppConfigBuilder.build {
appName = 'MyService'
version = '2.1.0'
environment(DB_HOST: 'localhost', CACHE_TTL: '300')
feature 'auth'
feature 'rate-limiting'
feature 'websockets'
}
println "DSL builder: ${config3}"
Output
Named params: ServerConfig(host:api.example.com, port:443, ssl:true, maxConnections:100, logLevel:INFO) Builder: DatabaseConfig(url:jdbc:postgresql://db.example.com:5432/mydb, username:admin, password:s3cret, poolSize:25, timeout:60000, autoCommit:true) DSL builder: App[MyService v2.1.0, features=[auth, rate-limiting, websockets], env=[DB_HOST:localhost, CACHE_TTL:300]]
What happened here: Groovy gives you three ways to build objects, from simplest to most powerful. Named parameters work for flat objects where you just need to set properties – no builder class needed at all. @Builder generates a fluent builder with chained method calls at compile time – identical to what you would hand-write in Java, but in one annotation. The closure-based DSL builder uses @DelegatesTo and Closure.DELEGATE_FIRST to create a mini-language for configuration – this is the pattern used by Gradle, Grails, and Spock. Choose named params for simple cases, @Builder for APIs, and closure DSLs for complex hierarchical configuration.
Example 8: Singleton Pattern with @Singleton
What we’re doing: Using Groovy’s @Singleton annotation for thread-safe singletons with zero boilerplate.
Example 8: Singleton Pattern with @Singleton
import groovy.transform.ToString
// --- Eager singleton (default) ---
@Singleton
class Registry {
private Map<String, Object> services = [:]
void register(String name, Object service) { services[name] = service }
Object lookup(String name) { services[name] }
int size() { services.size() }
}
// Access via .instance
Registry.instance.register('database', [host: 'localhost', port: 5432])
Registry.instance.register('cache', [host: 'localhost', port: 6379])
println "Registry size: ${Registry.instance.size()}"
println "DB config: ${Registry.instance.lookup('database')}"
// Same instance everywhere
def r1 = Registry.instance
def r2 = Registry.instance
println "Same instance: ${r1.is(r2)}"
// --- Lazy singleton ---
@Singleton(lazy = true)
class ExpensiveService {
ExpensiveService() {
println "[Init] ExpensiveService created (lazy)"
}
String process(String data) { "Processed: ${data.toUpperCase()}" }
}
println "\nBefore first access..."
println "Accessing now:"
println ExpensiveService.instance.process('hello groovy')
println ExpensiveService.instance.process('design patterns')
// --- @Singleton prevents new instances ---
try {
def illegal = new Registry()
} catch (RuntimeException e) {
println "\nCannot instantiate: ${e.message}"
}
Output
Registry size: 2 DB config: [host:localhost, port:5432] Same instance: true Before first access... Accessing now: [Init] ExpensiveService created (lazy) Processed: HELLO GROOVY Processed: DESIGN PATTERNS Cannot instantiate: Can't instantiate singleton Registry. Use Registry.instance
What happened here: @Singleton transforms your class at compile time: it makes the constructor private, creates a static instance property, and prevents instantiation with new. The default is eager initialization (instance created when the class loads). With lazy = true, the instance is created on first access using double-checked locking for thread safety. Notice that the lazy ExpensiveService constructor runs only when .instance is first called. In Java, you would need to write double-checked locking with volatile yourself – Groovy does it in one annotation.
Example 9: Pimp My Library with Categories
What we’re doing: Adding methods to existing classes (including JDK classes) using Groovy’s use(Category) pattern – scoped and safe.
Example 9: Pimp My Library with Categories
// Category: static methods where 1st param is the type being extended
class StringExtensions {
static String toSlug(String self) {
self.toLowerCase()
.replaceAll(/[^a-z0-9\s-]/, '')
.replaceAll(/\s+/, '-')
.replaceAll(/-+/, '-')
.trim()
}
static String truncate(String self, int maxLen) {
self.size() <= maxLen ? self : self[0..<maxLen] + '...'
}
static String mask(String self, int visibleChars = 4) {
if (self.size() <= visibleChars) return self
('*' * (self.size() - visibleChars)) + self[-visibleChars..-1]
}
}
class NumberExtensions {
static String toCurrency(Number self, String symbol = '$') {
"${symbol}${String.format('%.2f', self)}"
}
static boolean isEven(Number self) { self % 2 == 0 }
static boolean isOdd(Number self) { self % 2 != 0 }
static String toFileSize(Number self) {
def units = ['B', 'KB', 'MB', 'GB', 'TB']
def value = self.doubleValue()
def idx = 0
while (value >= 1024 && idx < units.size() - 1) {
value /= 1024
idx++
}
"${String.format('%.1f', value)} ${units[idx]}"
}
}
// Categories are scoped - only active inside the use() block
use(StringExtensions) {
println 'Hello World! This is Groovy'.toSlug()
println 'A very long description that needs truncation'.truncate(20)
println 'secret-api-key-12345'.mask(4)
println '4111111111111111'.mask(4)
}
use(NumberExtensions) {
println 1299.99.toCurrency()
println 42.isEven()
println 17.isOdd()
println 1_073_741_824.toFileSize()
println 2_500_000.toFileSize()
}
// Outside use() block, methods don't exist
try {
'test'.toSlug()
} catch (MissingMethodException e) {
println "\nOutside use(): ${e.message.split('\n')[0]}"
}
Output
hello-world-this-is-groovy A very long descript... ****************2345 ************1111 $1299.99 true true 1.0 GB 2.4 MB Outside use(): No signature of method: java.lang.String.toSlug()
What happened here: Categories let you add methods to classes you do not own – including String, Number, and any JDK class. The key difference from monkey-patching is scoping: the added methods only exist inside the use() block. Outside it, the classes are untouched. This is Scala’s “Pimp My Library” pattern (now called extension methods). The category class uses static methods where the first parameter is the type being extended. Groovy’s GDK itself uses this mechanism to add methods like .collect() and .each() to Java collections.
Example 10: Chain of Responsibility with Closures
What we’re doing: Building a request processing pipeline where each handler decides whether to process the request or pass it to the next handler.
Example 10: Chain of Responsibility with Closures
class RequestPipeline {
private List<Closure> handlers = []
RequestPipeline addHandler(Closure handler) {
handlers << handler
return this // fluent API
}
Map process(Map request) {
for (handler in handlers) {
request = handler(request)
if (request.halted) {
println "[Pipeline] Halted by: ${request.haltedBy}"
return request
}
}
return request
}
}
// Define handlers as closures
def authHandler = { req ->
println "[Auth] Checking token..."
if (!req.token) {
return req + [halted: true, haltedBy: 'auth', status: 401, error: 'No token provided']
}
if (req.token == 'expired') {
return req + [halted: true, haltedBy: 'auth', status: 401, error: 'Token expired']
}
println "[Auth] Token valid"
return req + [authenticated: true, userId: 'user-42']
}
def rateLimitHandler = { req ->
println "[RateLimit] Checking rate..."
def requestCount = req.requestCount ?: 0
if (requestCount > 100) {
return req + [halted: true, haltedBy: 'rate-limit', status: 429, error: 'Too many requests']
}
println "[RateLimit] Under limit (${requestCount}/100)"
return req
}
def loggingHandler = { req ->
println "[Logger] ${req.method} ${req.path} by ${req.userId ?: 'anonymous'}"
return req + [logged: true]
}
def responseHandler = { req ->
println "[Response] Building response..."
return req + [status: 200, body: "OK: ${req.path}"]
}
// Build the pipeline
def pipeline = new RequestPipeline()
.addHandler(authHandler)
.addHandler(rateLimitHandler)
.addHandler(loggingHandler)
.addHandler(responseHandler)
// Test 1: Valid request
println "=== Valid Request ==="
def result1 = pipeline.process([method: 'GET', path: '/api/users', token: 'valid-jwt', requestCount: 42])
println "Result: status=${result1.status}, body=${result1.body}\n"
// Test 2: No token
println "=== Missing Token ==="
def result2 = pipeline.process([method: 'GET', path: '/api/secret'])
println "Result: status=${result2.status}, error=${result2.error}\n"
// Test 3: Rate limited
println "=== Rate Limited ==="
def result3 = pipeline.process([method: 'POST', path: '/api/data', token: 'valid-jwt', requestCount: 150])
println "Result: status=${result3.status}, error=${result3.error}"
Output
=== Valid Request === [Auth] Checking token... [Auth] Token valid [RateLimit] Checking rate... [RateLimit] Under limit (42/100) [Logger] GET /api/users by user-42 [Response] Building response... Result: status=200, body=OK: /api/users === Missing Token === [Auth] Checking token... [Pipeline] Halted by: auth Result: status=401, error=No token provided === Rate Limited === [Auth] Checking token... [Auth] Token valid [RateLimit] Checking rate... [Pipeline] Halted by: rate-limit Result: status=429, error=Too many requests
What happened here: The Chain of Responsibility pattern in Java requires a Handler abstract class with a next reference, concrete handler subclasses, and manual chain wiring. In Groovy, each handler is a closure in a list. The pipeline iterates the list and stops when a handler sets halted: true. Adding, removing, or reordering handlers is just list manipulation. The immutable map approach (req + [key: value]) ensures each handler gets a fresh context without mutating the original request.
Example 11: Template Method with Closures
What we’re doing: Implementing the Template Method pattern where the algorithm skeleton is fixed but individual steps are customizable closures.
Example 11: Template Method with Closures
class DataExporter {
Closure fetchData
Closure transformData
Closure formatOutput
Closure writeOutput
// Template method - fixed algorithm, pluggable steps
String export() {
println "[1/4] Fetching data..."
def raw = fetchData()
println "[2/4] Transforming data..."
def transformed = transformData(raw)
println "[3/4] Formatting output..."
def formatted = formatOutput(transformed)
println "[4/4] Writing output..."
writeOutput(formatted)
return formatted
}
}
// CSV exporter
def csvExporter = new DataExporter(
fetchData: { [[name: 'Nirranjan', age: 30], [name: 'Viraj', age: 25], [name: 'Carol', age: 35]] },
transformData: { data -> data.findAll { it.age >= 28 } },
formatOutput: { data -> "name,age\n" + data.collect { "${it.name},${it.age}" }.join('\n') },
writeOutput: { content -> println "--- CSV Output ---\n${content}" }
)
csvExporter.export()
println ""
// JSON exporter - same template, different steps
def jsonExporter = new DataExporter(
fetchData: { [[id: 1, status: 'active'], [id: 2, status: 'inactive'], [id: 3, status: 'active']] },
transformData: { data -> data.findAll { it.status == 'active' } },
formatOutput: { data -> groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(data)) },
writeOutput: { content -> println "--- JSON Output ---\n${content}" }
)
jsonExporter.export()
Output
[1/4] Fetching data...
[2/4] Transforming data...
[3/4] Formatting output...
[4/4] Writing output...
--- CSV Output ---
name,age
Nirranjan,30
Carol,35
[1/4] Fetching data...
[2/4] Transforming data...
[3/4] Formatting output...
[4/4] Writing output...
--- JSON Output ---
[
{
"id": 1,
"status": "active"
},
{
"id": 3,
"status": "active"
}
]
What happened here: The Template Method pattern normally requires abstract classes with abstract methods that subclasses override. In Groovy, we replace inheritance with composition – the algorithm skeleton lives in export() and the variable steps are closures set as properties. Swapping from CSV to JSON export means swapping four closures, not creating a new subclass. This approach is more flexible because you can mix and match steps: use the CSV formatter with the JSON fetcher, or change just the transform step without touching anything else.
Example 12: Composite Pattern with Groovy Trees
What we’re doing: Building a file system tree using the Composite pattern, leveraging Groovy’s operator overloading and recursive closures.
Example 12: Composite Pattern with Groovy Trees
class FileNode {
String name
int size
boolean isDir
List<FileNode> children = []
// Operator overloading: dir << file
FileNode leftShift(FileNode child) {
if (!isDir) throw new UnsupportedOperationException("${name} is not a directory")
children << child
return this
}
int totalSize() {
isDir ? children.sum { it.totalSize() } as int : size
}
int fileCount() {
isDir ? children.sum { it.fileCount() } as int : 1
}
String tree(String indent = '') {
def icon = isDir ? '📁' : '📄'
def sizeStr = isDir ? "[${totalSize()} bytes, ${fileCount()} files]" : "[${size} bytes]"
def result = "${indent}${icon} ${name} ${sizeStr}\n"
if (isDir) {
children.each { result += it.tree(indent + ' ') }
}
return result
}
}
// Helper factories
def dir = { String name -> new FileNode(name: name, isDir: true) }
def file = { String name, int size -> new FileNode(name: name, size: size, isDir: false) }
// Build tree using << operator
def root = dir('project')
def src = dir('src')
def test = dir('test')
root << src << test << file('build.gradle', 850) << file('README.md', 2400)
src << file('App.groovy', 3200) << file('Config.groovy', 1800) << file('Utils.groovy', 950)
test << file('AppTest.groovy', 2100) << file('ConfigTest.groovy', 1500)
println root.tree()
println "Total project size: ${root.totalSize()} bytes"
println "Total files: ${root.fileCount()}"
// Find large files
def findLargeFiles
findLargeFiles = { FileNode node, int threshold ->
def results = []
if (node.isDir) {
node.children.each { results.addAll(findLargeFiles(it, threshold)) }
} else if (node.size > threshold) {
results << node
}
return results
}
println "\nFiles larger than 2000 bytes:"
findLargeFiles(root, 2000).each { println " ${it.name}: ${it.size} bytes" }
Output
📁 project [12800 bytes, 7 files]
📁 src [5950 bytes, 3 files]
📄 App.groovy [3200 bytes]
📄 Config.groovy [1800 bytes]
📄 Utils.groovy [950 bytes]
📁 test [3600 bytes, 2 files]
📄 AppTest.groovy [2100 bytes]
📄 ConfigTest.groovy [1500 bytes]
📄 build.gradle [850 bytes]
📄 README.md [2400 bytes]
Total project size: 12800 bytes
Total files: 7
Files larger than 2000 bytes:
App.groovy: 3200 bytes
AppTest.groovy: 2100 bytes
README.md: 2400 bytes
What happened here: The Composite pattern treats individual objects and compositions uniformly. Both files and directories are FileNode instances. totalSize() and fileCount() recurse into children for directories and return leaf values for files. Groovy’s operator overloading (leftShift maps to <<) makes tree construction read naturally. The recursive findLargeFiles closure demonstrates how Groovy closures can reference themselves for tree traversal. In Java, this pattern requires separate File and Directory classes implementing a Component interface – Groovy handles it with one class and a boolean flag.
Common Pitfalls
Pitfall 1: Closures as Strategies Losing Type Safety
When using closures as strategies, you lose compile-time type checking. A misspelled method or wrong parameter count fails at runtime, not compile time.
Pitfall: Type Safety with Closure Strategies
// BAD - no type checking, fails at runtime
def strategies = [
calc: { a, b -> a + b }
]
// This typo compiles fine but crashes at runtime:
// strategies.clac(1, 2) // NullPointerException - 'clac' returns null
// GOOD - use @CompileStatic with typed interfaces when safety matters
import groovy.transform.CompileStatic
interface Calculator {
int compute(int a, int b)
}
@CompileStatic
class SafeEngine {
Calculator strategy
int run(int a, int b) {
strategy.compute(a, b) // Compile-time checked
}
}
def engine = new SafeEngine(strategy: { int a, int b -> a + b } as Calculator)
println "Safe result: ${engine.run(10, 20)}"
Pitfall 2: @Delegate Method Conflicts
When two delegates have methods with the same name, the first @Delegate wins silently. This can cause hard-to-find bugs.
Pitfall: Delegate Method Conflicts
class Printer {
String print() { "Printer output" }
}
class Logger {
String print() { "Logger output" }
}
// BAD - Logger.print() is silently hidden
class Service {
@Delegate Printer printer = new Printer()
@Delegate Logger logger = new Logger()
}
def svc = new Service()
println svc.print() // Always "Printer output" - Logger.print() is lost
// GOOD - use @Delegate(excludes) or explicit methods
class BetterService {
@Delegate Printer printer = new Printer()
@Delegate(excludes = ['print']) Logger logger = new Logger()
String logPrint() { logger.print() }
}
def better = new BetterService()
println better.print() // Printer output
println better.logPrint() // Logger output
Pitfall 3: @Singleton with Mutable State
A singleton with mutable state shared across threads is a race condition waiting to happen. @Singleton guarantees one instance, not thread safety of its internals.
Pitfall: Mutable Singleton State
// BAD - mutable state in singleton, not thread-safe
@Singleton
class UnsafeCounter {
int count = 0
void increment() { count++ } // Race condition!
}
// GOOD - use synchronized or atomic operations
import java.util.concurrent.atomic.AtomicInteger
@Singleton
class SafeCounter {
private final AtomicInteger count = new AtomicInteger(0)
void increment() { count.incrementAndGet() }
int getCount() { count.get() }
}
SafeCounter.instance.increment()
SafeCounter.instance.increment()
SafeCounter.instance.increment()
println "Safe count: ${SafeCounter.instance.count}"
Conclusion
Design patterns in Groovy look fundamentally different from their Java counterparts. The Strategy pattern becomes a map of closures. The Observer pattern becomes a list of closures. The Decorator pattern collapses into @Delegate. The Singleton pattern is a single annotation. The Builder pattern has three Groovy-native approaches, each simpler than the Java original. And the Chain of Responsibility pattern is just iterating a closure list.
The common thread is that Groovy’s first-class closures, AST transformations, and dynamic features eliminate the structural ceremony that patterns require in Java. You still use the same mental models – strategy, observer, decorator, builder – but the implementation shrinks dramatically. The patterns are still there; they are just invisible, hidden behind language features that make them unnecessary as explicit structures.
If you want to explore more pattern-adjacent topics, read about Groovy Closures for deeper closure mechanics, Groovy Metaprogramming for runtime pattern customization, and Groovy AST Transformations for the compile-time magic behind @Delegate, @Singleton, and @Builder.
Best Practices
- DO use closures for behavioral patterns (Strategy, Observer, Command) – they eliminate interface boilerplate entirely.
- DO use
@Delegatefor Decorator and Delegation patterns – it generates all the forwarding code at compile time. - DO use
@Singletonwithlazy = truefor expensive-to-create singletons. - DO use named parameters for simple Builders,
@Builderfor APIs, and closure DSLs for complex configurations. - DO scope Category extensions with
use()blocks to avoid polluting global method resolution. - DON’T translate Java patterns line-for-line into Groovy – most patterns have a simpler idiomatic form.
- DON’T use mutable state in singletons without synchronization –
@Singletonguarantees one instance, not thread safety of fields. - DON’T ignore
@Delegatemethod conflicts – when two delegates share method names, the first wins silently. Useexcludesto be explicit. - DON’T use Categories for permanently needed methods – use Extension Modules instead for global, permanent method additions.
Frequently Asked Questions
How does Groovy simplify the Strategy pattern compared to Java?
In Java, the Strategy pattern requires an interface, concrete implementation classes, and a context class to swap between them. In Groovy, you replace the entire hierarchy with closures. A closure is essentially a single-method interface, so you can store strategies in a map, pass them as parameters, and swap them at runtime by reassigning a variable. No interface declarations, no implementation classes, no factory methods.
What does @Delegate do in Groovy?
The @Delegate annotation is an AST transformation that automatically generates forwarding methods from the containing class to the delegate object. If class A has @Delegate B b, then all public methods of B become callable on A without writing any forwarding code. You can override specific methods while letting the rest pass through. This implements both the Decorator and Delegation patterns with zero boilerplate. Use the excludes parameter to control method conflicts when delegating to multiple objects.
Is @Singleton thread-safe in Groovy?
Yes, @Singleton provides thread-safe instance creation. The default (eager) mode creates the instance at class loading time, which is inherently thread-safe. The lazy = true mode uses double-checked locking to ensure only one instance is created even under concurrent access. However, @Singleton only guarantees one instance – it does not make the singleton’s internal state thread-safe. If your singleton has mutable fields, you still need synchronization or atomic operations.
What is the Pimp My Library pattern in Groovy?
The Pimp My Library pattern (borrowed from Scala terminology) means adding methods to existing classes you don’t own, like String or Integer. In Groovy, you implement this with Categories: a class with static methods where the first parameter determines which type gets the new method. Wrap calls in use(MyCategoryClass) { ... } and the added methods are available inside that block only. This is scoped and safe – unlike monkey-patching, the methods disappear outside the use block.
When should I use @Builder vs named parameters in Groovy?
Use named parameters (new MyClass(field: value)) for simple, flat objects where you just need to set properties – it requires zero extra code. Use @Builder when you need a fluent API with method chaining, especially for library or framework code where the builder pattern communicates intent. Use closure-based DSL builders for complex hierarchical configurations, like Gradle build files or test fixtures, where the nested structure benefits from a mini-language.
Related Posts
Previous in Series: Groovy GDK Overview
Next in Series: Concurrency and GPars in Groovy
Related Topics You Might Like:
Up next: Concurrency and GPars in Groovy
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment