Runtime Metaprogramming & the MOP
|
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). Runtime metaprogramming is the ability to inspect and change how an object answers method calls and
property accesses while the program runs: intercepting calls, answering calls for methods that do not exist, and adding
methods to classes. All of it hangs on one idea, the Meta-Object Protocol (MOP): every Groovy call goes through a
MetaClass that decides what actually happens. Here the MOP means the GroovyObject hooks and the MetaClass they delegate
to; changing the metaclass itself is on
Metaclasses, ExpandoMetaClass & Categories,
adding methods to library classes from a jar is on Extension Modules,
and the compile-time counterpart is on
AST Transformations.
Runtime metaprogramming relies on dynamic dispatch. Code compiled with @CompileStatic, and in the stricter parts of
@TypeChecked, resolves calls at compile time and does not see hooks such as methodMissing unless a type checking extension
cooperates; see Type Checking Extensions.
The Meta-Object Protocol
A class compiled by Groovy implements GroovyObject, an interface with five methods. Every Groovy class gets them
automatically; a class written in Java can extend GroovyObjectSupport to get them too.
println GroovyObject.declaredMethods*.name.sort()
class Fan { def speed = 1; def rpm() { speed * 600 } }
def f = new Fan()
println f instanceof GroovyObject
println 'abc' instanceof GroovyObject
println f.metaClass.getClass().simpleName
println GroovySystem.metaClassRegistry.getMetaClass(Fan).getClass().simpleName
[getMetaClass, getProperty, invokeMethod, setMetaClass, setProperty]
true
false
HandleMetaClass
MetaClassImpl
Plain Java classes such as String are not GroovyObject`s, yet Groovy can call them dynamically all the same: the call goes to the
metaclass registered for the class in the `MetaClassRegistry (here MetaClassImpl). HandleMetaClass is a thin wrapper that
obj.metaClass returns so that obj.metaClass.something = ... changes only that object; the wrapper is the topic of the
metaclass page.
The hooks fall in three groups:
-
Calls:
invokeMethod(String, Object)runs when a method is called and cannot be found (or for every call on aGroovyInterceptable), andmethodMissing(String, Object)runs when a method cannot be found. -
Properties:
getProperty(String)andsetProperty(String, Object)run for every property access, andpropertyMissing(String)/propertyMissing(String, Object)when the property does not exist. -
Attributes:
getAttributeandsetAttributelive on theMetaClass, not onGroovyObject; they read and write the field itself, bypassing getters and setters.
Everything the compiler does for obj.foo(), obj.foo and obj.foo = 1 can also be done by hand through the metaclass:
class Fan {
def speed = 1
def rpm() { speed * 600 }
}
def f = new Fan()
println f.invokeMethod('rpm', null)
println f.metaClass.invokeMethod(f, 'rpm', [] as Object[])
println f.getProperty('speed')
f.setProperty('speed', 3)
println f.rpm()
println f.metaClass.getMetaMethod('rpm')?.name
println f.metaClass.getMetaProperty('speed').type.simpleName
println f.metaClass.properties*.name.sort()
600
600
1
1800
rpm
Object
[class, speed]
speed is declared with def, so its type is Object. Note that properties includes class, which every object has.
How a method call is resolved
When Groovy evaluates obj.name(args) it follows the steps in the flowchart below. The first step that applies decides the
result.
The next sections show each branch. This order was checked on Groovy 6.0.0 with a class that has a real method, a
closure-valued property, methodMissing and invokeMethod all at once:
class Order {
def hook = { x -> "closure property ${x}" }
def real() { 'real' }
def methodMissing(String n, Object a) { "methodMissing:${n}" }
def invokeMethod(String n, Object a) { "invokeMethod:${n}" }
}
def o = new Order()
println o.real()
println o.hook(1)
println o.ghost()
class OnlyInvoke { def invokeMethod(String n, Object a) { "invokeMethod:${n}" } }
println new OnlyInvoke().ghost()
class Neither {}
try { new Neither().ghost() } catch (MissingMethodException e) { println e.class.simpleName }
real
closure property 1
methodMissing:ghost
invokeMethod:ghost
MissingMethodException
Prefer methodMissing for "answer calls that do not exist": it cannot recurse into itself the way invokeMethod can, and it
composes with metaclass-added methods. Reach for invokeMethod mainly through GroovyInterceptable, when every call must pass
through one place.
invokeMethod
If a class defines invokeMethod(String, Object) it is called whenever the requested method does not exist on the class (and
there is no methodMissing). Methods that do exist run normally and invokeMethod is not involved.
class Zone {
String name
def invokeMethod(String name, Object args) {
"invokeMethod(${name}, ${args as List})"
}
}
def z = new Zone(name: 'north')
println z.water(5)
println z.toString().startsWith('Zone@')
println z.name
invokeMethod(water, [5])
true
north
Here water does not exist, so invokeMethod answered; toString() exists (inherited from Object), so it ran normally.
GroovyInterceptable
A class that implements the marker interface GroovyInterceptable has every method call routed to its invokeMethod, whether
the method exists or not, so it is the tool for cross-cutting behaviour such as tracing. Inside invokeMethod call the real
method through the metaclass, not through this, otherwise the call is intercepted again.
class Probe implements GroovyInterceptable {
def reading() { 21 }
def invokeMethod(String name, Object args) {
def m = metaClass.getMetaMethod(name, args)
System.out.println "before ${name}"
def r = m ? m.invoke(this, args) : "(no ${name})"
System.out.println "after ${name}"
r
}
}
def p = new Probe()
println p.reading()
println p.nothing()
before reading
after reading
21
before nothing
after nothing
(no nothing)
The calls to System.out.println are deliberate. A plain println inside the class is a method call on this, and this is
interceptable, so the interceptor calls itself until the stack overflows:
class Naive implements GroovyInterceptable {
def work() { 1 }
def invokeMethod(String name, Object args) {
println "calling ${name}" // println is a call on this: intercepted again
1
}
}
try { new Naive().work() } catch (StackOverflowError e) { println 'StackOverflowError' }
StackOverflowError
Interception covers calls made from outside and from inside the object, toString() and getClass() included:
class Mix implements GroovyInterceptable {
def foo() { 'foo' }
def invokeMethod(String n, Object a) { "intercepted:${n}" }
}
println new Mix().foo()
println new Mix().toString()
println new Mix().getClass()
intercepted:foo
intercepted:toString
intercepted:getClass
methodMissing
methodMissing(String name, Object args) is the more targeted hook: it runs only when no method (and no closure property) of that
name exists. A common pattern is to synthesise the method the first time it is requested and register it on the metaclass,
so later calls find a real method and skip methodMissing altogether.
class Sensor {
def methodMissing(String name, args) {
System.out.println "synthesising ${name}"
Sensor.metaClass."${name}" = { Object... a -> "value of ${name}" }
"value of ${name}"
}
}
def s = new Sensor()
println s.readTemp()
println s.readTemp()
println new Sensor().readTemp()
println new Sensor().readTemp()
synthesising readTemp
value of readTemp
synthesising readTemp
value of readTemp
value of readTemp
value of readTemp
The registered method is found by every instance created afterwards (the last two lines print without "synthesising"). The
instance s that triggered it had already cached its previous metaclass, so on Groovy 6.0.0 it kept calling methodMissing.
When it matters, register the method before creating instances, or compare with the per-instance approach on the metaclass
page.
To refuse names that do not follow the convention, throw MissingMethodException:
class Strict {
def methodMissing(String name, args) {
if (name.startsWith('read')) return "reading ${name - 'read'}"
throw new MissingMethodException(name, Strict, args)
}
}
def s = new Strict()
println s.readHumidity()
try { s.explode() } catch (MissingMethodException e) { println e.message.readLines()[0] }
reading Humidity
No signature of method: explode for class: Strict is applicable for argument types: () values: []
Properties: getProperty and setProperty
getProperty(String) and setProperty(String, Object) run for every property access on the object, existing or not, unless
the property is read as an attribute with .@. Delegate to the metaclass for the default behaviour.
class Bean {
String name = 'fern'
def getProperty(String p) {
if (p == 'shout') return name.toUpperCase()
metaClass.getProperty(this, p)
}
void setProperty(String p, Object v) {
if (p == 'name') v = v.trim()
metaClass.setProperty(this, p, v)
}
}
def b = new Bean()
println b.shout
b.name = ' moss '
println "[${b.name}]"
println b.@name
FERN
[moss]
moss
A side effect to remember: obj.metaClass is itself a property read, so it goes through your getProperty. In a class that
answers every name, u.metaClass is no longer the metaclass:
class Unknown {
def getProperty(String name) { "prop:${name}" }
}
def u = new Unknown()
println u.anything
println u.metaClass
println u.getMetaClass().getClass().simpleName
prop:anything
prop:metaClass
HandleMetaClass
Inside the class’s own code metaClass refers to the field directly, which is why metaClass.getProperty(this, p) above works.
Outside, call getMetaClass().
Attributes: getAttribute and setAttribute
An attribute is the raw field. obj.@name reads it directly without calling getName(). The same access is available as a
MOP operation through the metaclass:
class Gauge {
private String unit = 'C'
String getUnit() { 'unit is ' + unit }
}
def g = new Gauge()
println g.unit
println g.@unit
println g.metaClass.getAttribute(g, 'unit')
g.metaClass.setAttribute(g, 'unit', 'F')
println g.@unit
unit is C
C
C
F
getAttribute and setAttribute are not part of the GroovyObject interface (the list earlier has no such method); they are
MetaClass operations, so a custom metaclass can override them.
propertyMissing
propertyMissing(String name) answers reads and propertyMissing(String name, Object value) answers writes of properties
that do not exist. Like methodMissing, it is only called when normal lookup fails, so it is cheaper than overriding
getProperty.
class Readings {
def values = [:]
def propertyMissing(String name) { values[name] }
def propertyMissing(String name, value) { values[name] = value }
}
def r = new Readings()
r.temp = 22
r.humidity = 61
println r.temp + r.humidity
println r.values
println r.nothing
83
[temp:22, humidity:61]
null
Static variants
methodMissing and propertyMissing also exist for calls on the class itself. They are static methods named with the
prefix $static_: $static_methodMissing(String, Object) and $static_propertyMissing(String).
class Vent {
static def $static_methodMissing(String name, Object args) { "static ${name}${args.toList()}" }
static def $static_propertyMissing(String name) {
if (name == 'limit') return 80
throw new MissingPropertyException(name, Vent)
}
}
println Vent.dispatch(3)
println Vent.limit
static dispatch[3]
80
Source: Groovy documentation: Runtime metaprogramming, static methodMissing and static propertyMissing
$static_propertyMissing throws for names it does not know on purpose. When a class answers every static property name,
a call such as Vent.dispatch(3) is read first as the property dispatch and then called, and fails with
"No signature of method: call". Keep the property hook selective.
respondsTo and hasProperty
Because methods and properties can appear at run time, Groovy lets you ask the metaclass what a receiver supports. Both are
defined on MetaClass and exposed as GDK methods on every object. respondsTo(name) returns the list of matching MetaMethod`s
(empty if none), `respondsTo(name, argTypes) narrows by argument types, and hasProperty(name) returns the MetaProperty, or
null.
class Vent {
String open() { 'open' }
String open(int percent) { "open ${percent}%" }
}
def v = new Vent()
println v.respondsTo('open').size()
println v.respondsTo('open', [] as Object[]).size()
println v.respondsTo('open', 50).size()
println v.respondsTo('open', 'x').size()
println v.respondsTo('close').isEmpty()
println v.hasProperty('class')?.name
println v.hasProperty('nothing')
2
1
1
0
true
class
null
Use these before calling into an object whose shape you do not control: if (v.respondsTo('open', 50)) v.open(50). A method that a
class would answer only through methodMissing is not reported by respondsTo, which looks at what is registered, not at what
the hook would accept; that is why Groovy code that synthesises methods usually registers them on the metaclass.
Where the book is outdated
Older Groovy books describe invokeMethod as the way to build dynamic objects and give less weight to methodMissing and
propertyMissing, which arrived in Groovy 1.5 and are the preferred hooks today. The GroovyObject interface, the MetaClass
operations and the order of resolution shown here are the same on Groovy 6.0 as in the 2.x series; what changed around them
is the runtime underneath (Groovy 6.0 needs JDK 17 or later) and the growing use of @CompileStatic, which bypasses this
machinery.