Groovy Metaprogramming
last modified October 15, 2026
Metaprogramming lets a program inspect and change its own structure while it
runs. In Groovy this is not an exotic feature but a first-class part of the
language. Every object carries a MetaClass that mediates method
and property access, and you can intercept, add, or replace behavior at
runtime. This makes Groovy ideal for writing DSLs, mocks, and dynamic wrappers.
This tutorial explores the core metaprogramming tools with practical examples.
The MetaClass
Each class in Groovy is associated with a MetaClass, accessible
through the metaClass property. It holds the registry of methods
and properties that the runtime uses when you call a method or read a field.
Inspecting it reveals what Groovy knows about a type.
class User {
String name
}
def user = new User(name: "Alice")
def meta = user.metaClass
println meta.theClass
println meta.respondsTo(user, "getName")
println meta.respondsTo(user, "fly")
The theClass property returns the class the meta class describes.
respondsTo checks whether a method exists, so getName
returns true while fly returns false. This is the same lookup the
runtime performs, exposed for your own code to query.
$ groovy MetaClassIntro.groovy class User [public java.lang.String User.getName()] []
Adding Methods at Runtime
You can attach a new method to a class while the program runs by assigning a
closure to a name on its metaClass. Every existing and future
instance then gains the method.
class Greeter {}
Greeter.metaClass.greet = { String name -> "Hello, $name!" }
def greeter = new Greeter()
println greeter.greet("Alice")
println greeter.greet("Bob")
The Greeter class declares no methods, yet both instances respond
to greet. The closure becomes the method body, and its parameters
become the method parameters. This technique is handy for extending third-party
classes whose source you cannot change.
$ groovy AddMethod.groovy Hello, Alice! Hello, Bob!
Adding Properties at Runtime
Properties can be added the same way. Assigning a value on the meta class creates a field that all instances share unless they override it.
class Config {}
Config.metaClass.version = "1.0"
Config.metaClass.author = "Jan"
def config = new Config()
println config.version
println config.author
Both version and author are now readable on the
instance. This is a quick way to attach static-looking metadata to a type
without editing its definition.
$ groovy AddProperty.groovy 1.0 Jan
methodMissing
When Groovy cannot find a method, it calls methodMissing if the
class defines one. This hook lets you handle arbitrary method names, which is
the foundation of many Groovy DSLs.
class Recorder {
def calls = []
def methodMissing(String name, args) {
calls << "$name${args.toList()}"
"recorded $name"
}
}
def recorder = new Recorder()
println recorder.save("file.txt")
println recorder.load(42)
println recorder.calls
Any unknown method is routed to methodMissing, which stores the
name and arguments and returns a value. The Recorder behaves as if
it had a method for every name you throw at it. This pattern powers mocking
libraries and fluent builders.
$ groovy MethodMissing.groovy recorded save recorded load [save[file.txt], load[42]]
propertyMissing
Similarly, propertyMissing is invoked when a property cannot be
found. You can implement one method for reads and another for writes.
class Bag {
def store = [:]
def propertyMissing(String name) {
store[name]
}
void propertyMissing(String name, value) {
store[name] = value
}
}
def bag = new Bag()
bag.color = "blue"
bag.size = 10
println bag.color
println bag.store
Setting an unknown property stores it in the map, and reading it retrieves the
value. The Bag acts like a dynamic record with no predefined
fields, useful for loosely structured configuration or JSON-like data.
$ groovy PropertyMissing.groovy blue [color:blue, size:10]
ExpandoMetaClass
Adding several methods one by one is verbose. ExpandoMetaClass
provides a builder-style syntax to define many methods, properties, and even
static methods in a single block.
class MathLib {}
MathLib.metaClass {
square = { double x -> x * x }
cube = { double x -> x * x * x }
}
MathLib.metaClass.static.pi = { -> Math.PI }
def lib = new MathLib()
println lib.square(5)
println lib.cube(3)
println MathLib.pi()
The block registers the instance methods square and
cube in one place. Static methods are added through
metaClass.static, since a static modifier is not valid
inside the block. This keeps related extensions grouped and readable.
$ groovy ExpandoDemo.groovy 25.0 27.0 3.141592653589793
Adding Methods to a Single Instance
Extensions do not have to be global. By working with an instance's meta class, you can give one object a unique behavior while leaving its class untouched.
class Robot {}
def r2d2 = new Robot()
def c3po = new Robot()
r2d2.metaClass.beep = { "Beep boop!" }
println r2d2.beep()
println c3po.metaClass.respondsTo(c3po, "beep")
Only r2d2 gains beep. c3po reports that
it does not respond to it. Per-instance meta classes are invaluable when
testing a single collaborator in isolation.
$ groovy PerInstance.groovy Beep boop! []
Intercepting Method Calls
The invokeMethod hook receives calls that the object cannot resolve
to a real method. It lets a class respond to whole families of method names,
for example to log, validate, or forward calls.
class Calculator {
def invokeMethod(String name, args) {
println "calling $name with ${args.toList()}"
switch (name) {
case 'add': return args[0] + args[1]
case 'multiply': return args[0] * args[1]
default: throw new MissingMethodException(name, Calculator, args)
}
}
}
def calc = new Calculator()
println calc.add(2, 3)
println calc.multiply(4, 5)
The class defines no add or multiply methods. Each call
is logged and then dispatched by name, so the same hook can handle any number of
operations. Anything unknown raises MissingMethodException, which is
what Groovy would have thrown anyway.
$ groovy InvokeMethod.groovy calling add with [2, 3] 5 calling multiply with [4, 5] 20
Categories and use
A category is a class of static methods whose first parameter is the receiver.
The use block applies those methods to the enclosed code for the
duration of the block, a scoped alternative to permanent meta class changes.
class StringExtensions {
static String shout(String self) {
self.toUpperCase() + "!"
}
}
use(StringExtensions) {
println "hello".shout()
}
Inside the block, every String gains shout. Outside
it, the method disappears. Categories offer temporal, thread-local enrichment
that is perfect for localized domain-specific operations.
$ groovy Categories.groovy HELLO!
Best Practices for Metaprogramming
- Prefer Scoped Changes: Use categories or per-instance meta classes over global ones to limit the blast radius of your extensions.
- Keep It Discoverable: Document runtime methods, since IDEs cannot always see behavior added dynamically.
- Beware of Name Clashes: Adding methods to core classes can conflict with library code or future Groovy releases.
- Meter the Cost: Dynamic dispatch is slower than direct calls, so avoid using it in hot loops.
- Use It with Purpose: Reserve metaprogramming for DSLs, mocks, and glue code rather than everyday logic.
Source
Groovy Metaprogramming Documentation
In this tutorial, we explored Groovy metaprogramming through the
MetaClass, runtime method and property injection,
methodMissing, propertyMissing,
ExpandoMetaClass, and invokeMethod. These tools let
Groovy programs adapt their own structure at runtime, opening the door to
expressive DSLs and flexible frameworks.
Author
List all Groovy tutorials.