Design Patterns in Groovy
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Groovy 6.0 (6.0.0, 2026-09-22) and every example was run with Groovy 6.0.0 on JDK 26
(checked on 2026-10-14). The official guide lists 21 patterns; the examples here are original and use a small greenhouse
scenario (valves, pumps, sensors, tanks). The point of the guide, and of this page, is that many classic patterns shrink or
disappear in Groovy: closures replace single-method interfaces, @Delegate, @Singleton and @Bindable replace
boilerplate, categories and metaclasses let you add methods to existing classes, and dynamic dispatch removes the need for
double dispatch. The underlying features are on
Closures, Traits and
Functional Programming.
The patterns, in the order of the official guide:
Abstract Factory
Abstract Factory hides which family of related objects gets created behind one interface. In Groovy the factory interface is often just a closure, and the family selector a map of closures, so the class hierarchy collapses into data. The first two classes are the classic shape; the map at the end is the idiomatic shape.
abstract class SensorFactory { abstract Closure<String> reader() }
class MetricFactory extends SensorFactory { Closure<String> reader() { { double c -> "${c} C" } } }
class ImperialFactory extends SensorFactory {
Closure<String> reader() { { double c -> String.format(Locale.ROOT, '%.1f F', c * 9 / 5 + 32) } }
}
// Groovy way: a map of closures replaces the factory class hierarchy
def factories = [metric: { double c -> "${c} C" }, imperial: { double c -> String.format(Locale.ROOT, '%.1f F', c * 9 / 5 + 32) }]
println new MetricFactory().reader()(21.5d)
println new ImperialFactory().reader()(21.5d)
println factories.imperial(21.5d)
println factories.metric(21.5d)
21.5 C
70.7 F
70.7 F
21.5 C
Adapter
Adapter makes one interface look like another. Groovy offers four lightweight ways: coercing a closure or a map to the target interface with as, @Delegate to forward the methods that already match, and a category for adding the missing method to the adaptee temporarily.
class Thermometer { double celsius() { 21.5d } }
interface Reading { String text() }
// as coercion: a closure or a map becomes an implementation of the interface
Reading r1 = { 'closure adapter' } as Reading
Reading r2 = [text: { -> 'map adapter' }] as Reading
println r1.text()
println r2.text()
// adapter through delegation, no boilerplate
class ThermometerReading implements Reading {
@Delegate Thermometer t = new Thermometer()
String text() { "${celsius()} C" }
}
println new ThermometerReading().text()
// category adapter: temporary new method on an existing class
class Fahrenheit { static double toF(Thermometer t) { t.celsius() * 9 / 5 + 32 } }
use(Fahrenheit) { println new Thermometer().toF() }
closure adapter
map adapter
21.5 C
70.7
Bouncer
The Bouncer checks arguments at the door of a method and fails fast. Groovy’s assert carries an explanatory message and, thanks to Power Assert, prints the values involved; Objects.requireNonNull covers the JDK-style null check. For contract-style checks see Design by Contract with GContracts.
import static groovy.test.GroovyAssert.shouldFail
def water(Integer minutes, String plant) {
assert minutes != null && minutes > 0, 'minutes must be positive'
assert plant, 'plant name required'
"watering ${plant} for ${minutes} min"
}
println water(5, 'basil')
println shouldFail(AssertionError) { water(0, 'basil') }.message.readLines().first()
def pick(String s) { Objects.requireNonNull(s, 'name') }
println shouldFail(NullPointerException) { pick(null) }.message
watering basil for 5 min
minutes must be positive. Expression: ((minutes != null) && (minutes > 0)). Values: minutes = 0, minutes = 0
name
Chain of Responsibility
A request passes along handlers until one deals with it. A list of closures plus findResult expresses this in one line, because findResult returns the first non-null value. The linked-object form is shown after it for comparison.
// each handler is a closure returning a result or null to pass on
def lowMoisture = { Map r -> r.moisture < 20 ? 'ALERT: dry soil' : null }
def highTemp = { Map r -> r.temp > 35 ? 'ALERT: too hot' : null }
def ok = { Map r -> 'ok' }
def chain = [lowMoisture, highTemp, ok]
def handle = { Map r -> chain.findResult { it(r) } }
println handle([moisture: 10, temp: 20])
println handle([moisture: 50, temp: 40])
println handle([moisture: 50, temp: 20])
// object-style: each link holds the next one
abstract class Link {
Link next
String process(Map r) { handle(r) ?: next?.process(r) }
abstract String handle(Map r)
}
class Dry extends Link { String handle(Map r) { r.moisture < 20 ? 'dry' : null } }
class Hot extends Link { String handle(Map r) { r.temp > 35 ? 'hot' : null } }
def head = new Dry(next: new Hot())
println head.process([moisture: 50, temp: 40])
println head.process([moisture: 50, temp: 20])
ALERT: dry soil
ALERT: too hot
ok
hot
null
Command
Command turns an action into an object that can be stored, queued, replayed or undone. A closure is already such an object, so a list of closures is a command queue. When undo is needed, a small class with a pair of methods is clearer.
def log = []
def commands = [
{ log << 'open valve' },
{ log << 'run pump' },
{ log << 'close valve' }
]
commands.each { it() }
println log
// undo support with a small class
class Counter { int n }
class Add { Counter c; int k; void run() { c.n += k }; void undo() { c.n -= k } }
def c = new Counter()
def cmds = [new Add(c: c, k: 5), new Add(c: c, k: 3)]
cmds*.run()
println c.n
cmds.reverse()*.undo()
println c.n
[open valve, run pump, close valve]
8
0
Composite
Composite treats a single object and a group of objects uniformly. Groovy’s leftShift operator (<<) gives the tree a natural way to be built, and nested maps and lists are composites for free, walked with a recursive closure.
abstract class Node { String name; abstract int liters() }
class Valve extends Node { int flow; int liters() { flow } }
class Zone extends Node {
List<Node> children = []
Zone leftShift(Node n) { children << n; this }
int liters() { children.sum { it.liters() } ?: 0 }
}
def bed = new Zone(name: 'bed') << new Valve(name: 'a', flow: 5) << new Valve(name: 'b', flow: 7)
def greenhouse = new Zone(name: 'greenhouse') << bed << new Valve(name: 'c', flow: 10)
println greenhouse.liters()
// maps and lists compose naturally too
def tree = [a: 5, bed: [b: 7, c: 3], d: 10]
def total
total = { it instanceof Map ? it.values().sum { v -> total(v) } : it }
println total(tree)
22
25
Decorator
Decorator adds behaviour to an object by wrapping it in another object with the same interface. The boilerplate of forwarding every other method is what @Delegate removes: the wrapper overrides only the decorated method. For functions, closure composition with >> does the same job. The class structure is shown below.
interface Logger { void log(String m) }
class ConsoleLogger implements Logger { void log(String m) { println m } }
// @Delegate forwards every Logger method; override only what is decorated
class TimestampLogger implements Logger {
@Delegate Logger inner
void log(String m) { inner.log("[t=0] $m") }
}
class UpperLogger implements Logger {
@Delegate Logger inner
void log(String m) { inner.log(m.toUpperCase()) }
}
Logger l = new UpperLogger(inner: new TimestampLogger(inner: new ConsoleLogger()))
l.log('pump started')
// closure decorators compose with >>
def shout = { String s -> s.toUpperCase() }
def stamp = { String s -> "[t=0] $s" }
def both = shout >> stamp
println both('pump started')
[t=0] PUMP STARTED
[t=0] PUMP STARTED
Each decorator both implements Logger and holds a Logger, so decorators nest to any depth. @Delegate generates the
forwarding for every method the decorator does not override. The same relationship, with a concrete helper instead of an
interface, is the Delegation pattern below.
Delegation
Delegation forwards work to a helper instead of inheriting from it. @Delegate generates the forwarding methods at compile time; a method written explicitly in the class takes precedence. When the target is only known at run time, methodMissing forwards dynamically.
class Sprinkler { String spray() { 'spraying' }; int flow() { 5 } }
class Controller {
@Delegate Sprinkler sprinkler = new Sprinkler()
String spray() { 'controller: ' + sprinkler.spray() } // explicit method wins
}
def c = new Controller()
println c.spray()
println c.flow()
println Controller.declaredMethods*.name.contains('flow')
// dynamic delegation through methodMissing
class Forwarder {
def target = new Sprinkler()
def methodMissing(String name, args) { target."$name"(*args) }
}
println new Forwarder().flow()
controller: spraying
5
true
5
Flyweight
Flyweight shares immutable instances instead of creating many equal ones. An @Immutable class is the safe candidate for sharing, and a Map.computeIfAbsent cache is the factory. The JDK already applies the pattern to small boxed integers and interned strings.
@groovy.transform.Immutable class Species { String name; int daysToHarvest }
class SpeciesPool {
static final Map<String, Species> cache = [:]
static Species of(String n) { cache.computeIfAbsent(n) { new Species(it, 60) } }
}
def a = SpeciesPool.of('basil')
def b = SpeciesPool.of('basil')
println a.is(b)
println SpeciesPool.cache.size()
// built-in flyweights: interned Strings and small boxed integers
println(Integer.valueOf(100).is(Integer.valueOf(100)))
println('abc'.intern().is('abc'.intern()))
true
1
true
true
Iterator
Iterator hides how a collection is traversed. In Groovy every iterable, range, string and map answers each, and any class implementing Iterable works in a for loop and gets all GDK collection methods.
[1, 2, 3].each { print "$it " }
println()
(1..3).each { print "$it " }
println()
'abc'.each { print "$it " }
println()
[a: 1, b: 2].each { k, v -> print "$k=$v " }
println()
// a custom iterable works with for-in and every GDK collection method
class Rows implements Iterable<Integer> {
Iterator<Integer> iterator() { (1..3).iterator() }
}
for (r in new Rows()) print "row$r "
println()
println new Rows().collect { it * 10 }
def it2 = [10, 20].iterator()
while (it2.hasNext()) println it2.next()
1 2 3
1 2 3
a b c
a=1 b=2
row1 row2 row3
[10, 20, 30]
10
20
Loan my Resource
The resource owner opens and releases the resource and lends it to a closure. The GDK uses this for files (withWriter, eachLine, withReader) and streams, so the client cannot forget to close; write your own with try/finally around the call of the closure.
def withTempFile = { Closure body ->
def f = File.createTempFile('loan', '.txt')
try { body(f) } finally { f.delete(); println "deleted: ${!f.exists()}" }
}
withTempFile { f -> f.text = 'hello'; println f.text }
// the GDK applies the pattern everywhere
def f = File.createTempFile('loan', '.txt')
f.withWriter { it << 'line1\nline2\n' }
f.eachLine { println it }
f.withReader { println it.readLine() }
f.delete()
hello
deleted: true
line1
line2
line1
Monoids
A monoid is an associative operation with an identity element: addition with 0, string concatenation with the empty string, list concatenation with the empty list. inject with the identity as the initial value is the generic fold, and the identity makes the empty collection safe. Associativity is what allows a fold to be split and run in parallel.
// a monoid: an associative operation plus an identity element
def sum = [1, 2, 3, 4].inject(0) { a, b -> a + b }
def concat = ['a', 'b', 'c'].inject('') { a, b -> a + b }
def lists = [[1], [2, 3], []].inject([]) { a, b -> a + b }
println "$sum $concat $lists"
// the identity makes the empty case safe
println([].inject(0) { a, b -> a + b })
// a generic fold
def fold = { Collection xs, identity, Closure op -> xs.inject(identity, op) }
println fold([2, 3, 4], 1) { a, b -> a * b }
// parallel-safe because of associativity
println fold(1..100, 0) { a, b -> a + b }
println(((1..50).sum() + (51..100).sum()) == (1..100).sum())
10 abc [1, 2, 3]
0
24
5050
true
Null Object
A Null Object replaces null with an object that does nothing sensible. Groovy has cheaper tools as well: the safe-navigation operator ?., the Elvis operator ?:, Optional, and withDefault for maps.
class Sensor { String read() { '21.5' } }
class NullSensor extends Sensor { String read() { 'n/a' } }
def find = { String id -> id == 'a1' ? new Sensor() : new NullSensor() }
println find('a1').read()
println find('zz').read()
// the Groovy shortcuts
Sensor s = null
println s?.read()
println s?.read() ?: 'n/a'
println Optional.ofNullable(s).map { it.read() }.orElse('n/a')
// a map as a Null Object via default
def prefs = [unit: 'C'].withDefault { 'unset' }
println prefs.unit
println prefs.missing
21.5
n/a
null
n/a
n/a
C
unset
Observer
Observer notifies registered listeners when a subject changes. @Bindable generates the java.beans property-change support, and closures are accepted wherever a listener is expected, or kept in a plain list.
import java.beans.*
import groovy.beans.Bindable
class Tank {
@Bindable int level
}
def t = new Tank()
t.addPropertyChangeListener { PropertyChangeEvent e -> println "${e.propertyName}: ${e.oldValue} -> ${e.newValue}" }
t.level = 40
t.level = 55
// plain closures as listeners
class Subject {
List<Closure> listeners = []
void fire(x) { listeners.each { it(x) } }
}
def s = new Subject()
s.listeners << { println "A got $it" } << { println "B got $it" }
s.fire('rain')
level: 0 -> 40
level: 40 -> 55
A got rain
B got rain
Pimp my Library
Add methods to a class you do not own. Groovy has several mechanisms with different scope: a category (temporary and scoped), ExpandoMetaClass (global), an extension module (compiled in and visible to the type checker) and traits applied to one object at run time with withTraits. See Metaclasses, ExpandoMetaClass & Categories and Extension Modules.
// 1. Category (temporary, scoped)
class Greenhouse { static String shout(String s) { s.toUpperCase() + '!' } }
use(Greenhouse) { println 'watering'.shout() }
// 2. ExpandoMetaClass (global)
Integer.metaClass.minutes = { -> delegate * 60 }
println 5.minutes()
String.metaClass.isPlantName = { -> delegate ==~ /[a-z]+/ }
println 'basil'.isPlantName()
// 3. Extension module: a static method in a class registered in META-INF/groovy
// (see the extension modules page)
// 4. Traits applied at runtime
trait Waterable { String water() { "watering ${name}" } }
class Plant { String name }
def p = new Plant(name: 'mint').withTraits(Waterable)
println p.water()
WATERING!
300
true
watering mint
Proxy
A proxy stands in for another object to control access to it. Groovy offers an invokeMethod forwarder, a ProxyMetaClass with an interceptor that wraps calls to an existing class without touching it, and as coercion of a closure to an interface, which creates a dynamic proxy.
class Pump { String run() { 'running' } }
class PumpProxy {
private final Pump target = new Pump()
def invokeMethod(String name, args) {
println "before $name"
def r = target.invokeMethod(name, args)
println "after $name"
r
}
}
println new PumpProxy().run()
// ProxyMetaClass: intercept calls without touching the class
def pmc = ProxyMetaClass.getInstance(Pump)
pmc.interceptor = new TracingInterceptor(writer: new PrintWriter(System.out))
pmc.use { new Pump().run() }
// dynamic proxy for an interface through coercion
Runnable r = { println 'inside' } as Runnable
r.run()
before run
after run
running
before Pump.ctor()
after Pump.ctor()
before Pump.run()
after Pump.run()
inside
Singleton
@Singleton makes the constructor private and adds a static instance property. With lazy = true the instance is created on first access; strict = false leaves the constructor usable by the class itself and only warns instead of throwing for others. See AST Transformations: Code Generation & Class Design.
@Singleton
class Config {
String unit = 'C'
}
println Config.instance.unit
println Config.instance.is(Config.instance)
try { new Config() } catch (e) { println e.class.simpleName }
@Singleton(lazy = true, strict = false)
class Lazy { Lazy() { println 'created' } }
println 'before'
Lazy.instance
println 'after'
Lazy.instance
C
true
RuntimeException
before
created
after
State
State lets an object change behaviour when its state changes. An enum with an abstract method gives each state its own behaviour; for lighter cases a map from state name to closure, or a switch over the state, is enough.
enum Valve {
CLOSED { Valve next() { OPEN } },
OPEN { Valve next() { CLOSED } }
abstract Valve next()
}
def v = Valve.CLOSED
3.times { v = v.next(); println v }
// state as a map of closures, swapped at runtime
class Pump {
def states = [idle: { 'idle: start?' }, running: { 'running: stop?' }]
String state = 'idle'
String act() { states[state]() }
}
def p = new Pump()
println p.act()
p.state = 'running'
println p.act()
// switch on state
def describe = { s -> switch (s) { case Valve.OPEN: 'flowing'; break; default: 'blocked' } }
println describe(Valve.OPEN)
println describe(Valve.CLOSED)
OPEN
CLOSED
OPEN
idle: start?
running: stop?
flowing
blocked
Strategy
Strategy picks an algorithm at run time. Closures are strategies, and a map of closures is the registry. A closure is also coerced to any single-abstract-method interface, so existing Java strategy interfaces accept one directly.
def strategies = [
drip: { int m -> m * 2 },
sprinkle:{ int m -> m * 5 },
]
strategies.each { name, f -> println "$name: ${f(10)} L" }
interface Calc { int liters(int m) }
Calc c = { int m -> m * 3 } as Calc // closure coercion to an interface (SAM)
println c.liters(10)
Calc d = { m -> m + 1 } // implicit SAM coercion in Groovy 2.2+
println d.liters(10)
def choose = { String n -> strategies[n] ?: strategies.drip }
println choose('sprinkle')(2)
println choose('none')(2)
drip: 20 L
sprinkle: 50 L
30
11
10
4
Template Method
A skeleton algorithm fixes the order of steps and lets subclasses fill in some of them. The inheritance form uses a final method calling overridable steps; the closure form passes the variable step as a parameter.
abstract class Job {
final String run() { [prepare(), execute(), cleanup()].join(' | ') } // the template
String prepare() { 'prepare' }
abstract String execute()
String cleanup() { 'cleanup' }
}
println new Job() { String execute() { 'water' } }.run()
println new Job() { String execute() { 'fertilize' }; String cleanup() { 'rinse' } }.run()
// closure version
def job = { Closure body -> "prepare | ${body()} | cleanup" }
println job { 'water' }
prepare | water | cleanup
prepare | fertilize | rinse
prepare | water | cleanup
Visitor
Visitor separates an operation from the structure it works on. A closure accepted by the nodes is the lightest visitor. Because Groovy dispatches overloads on the run-time type of the argument (multi-methods), a visitor class does not need the double-dispatch accept ceremony of Java.
abstract class Node { abstract accept(Closure v) }
class Leaf extends Node { int liters; def accept(Closure v) { v(this) } }
class Branch extends Node {
List<Node> kids = []
def accept(Closure v) { v(this); kids*.accept(v) }
}
def tree = new Branch(kids: [new Leaf(liters: 5), new Branch(kids: [new Leaf(liters: 7)])])
def total = 0
tree.accept { n -> if (n instanceof Leaf) total += n.liters }
println total
// multi-methods: Groovy picks the overload at runtime from the argument
class Printer {
String visit(Leaf l) { "leaf ${l.liters}" }
String visit(Branch b) { "branch of ${b.kids.size()}" }
}
def pr = new Printer()
println([tree, tree.kids[0]].collect { Node n -> pr.visit(n) })
12
[branch of 2, leaf 5]
Where the book is outdated
The Manning book Groovy in Action (publisher page in the references) covers closures, categories, ExpandoMetaClass and
builders, which carry most of these patterns, and those parts still hold. What it cannot show are the later tools used above:
@Delegate, @Singleton, @Bindable, traits with withTraits, as coercion of closures to every single-method
interface, and the safe-navigation based Null Object shortcuts that are common today. For where each appeared see
What’s New & Migrating from Older Versions.