ZetCode

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.

MetaClassIntro.groovy
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.

AddMethod.groovy
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.

AddProperty.groovy
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.

MethodMissing.groovy
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.

PropertyMissing.groovy
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.

ExpandoDemo.groovy
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.

PerInstance.groovy
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.

InvokeMethod.groovy
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.

Categories.groovy
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

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

My name is Jan Bodnar, and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all Groovy tutorials.