This episode covers Groovy metaprogramming: dynamic methods, propertyMissing, and methodMissing. You will also compare runtime metaprogramming with compile-time AST transformations, and study decorator and dynamic proxy pattern examples.

This is Groovy's biggest differentiator: metaprogramming. The ability to change program behavior at runtime lets Groovy do things that require large frameworks in other languages.
class Dinamis {
def methodMissing(String nama, args) {
println "Memanggil method ${nama} dengan argumen ${args}"
}
}
def obj = new Dinamis()
obj.halo()
obj.kirim("pesan", 123)obj.halo() calls a method that doesn't exist — Groovy forwards it to methodMissing. def methodMissing(String nama, args) receives the method name and arguments, so a class can respond to any call dynamically.
Use methodMissing for fallback behavior, since invokeMethod adds overhead to every call.
class Konfigurasi {
def propertyMissing(String nama) {
println "Property ${nama} tidak ditemukan, mengembalikan default"
return null
}
}
def config = new Konfigurasi()
println config.tidakAdaconfig.tidakAda triggers propertyMissing because that property isn't defined. def propertyMissing(String nama) receives the property name and returns a substitute value — a useful pattern for configuration with fallbacks.
class Store {
def storage = [:]
def propertyMissing(String nama, value) {
storage[nama] = value
}
}
def store = new Store()
store.nama = "Arman"
println store.storagestore.nama = "Arman" triggers propertyMissing(String nama, value) and stores the value in an internal map. def propertyMissing(String nama, value) is the two-argument variant for handling assignments.
String.metaClass.isPalindrome = {
delegate == delegate.reverse()
}
println "kodok".isPalindrome()
println "groovy".isPalindrome()String.metaClass.isPalindrome = { ... } adds a new method to the String class. delegate inside the closure refers to the string instance, so "kodok".isPalindrome() can be called directly.
List.metaClass.rataRata = {
delegate.isEmpty() ? 0 : delegate.sum() / delegate.size()
}
println [1, 2, 3, 4].rataRata()List.metaClass.rataRata = { ... } makes a rataRata method available for all lists. delegate.isEmpty() ? 0 : delegate.sum() / delegate.size() uses Elvis to handle empty lists.
@Canonical and @Builder modify code at compile time.The runtime approach is the most flexible but slower; the compile-time approach produces fast code that IDEs can inspect.
Choose runtime metaprogramming for dynamic DSLs and fallbacks, because its behavior can change based on input. Choose AST transformations for code whose structure is already certain, because the results are faster and type-safe.
class LoggingProxy {
Object target
def methodMissing(String nama, args) {
println "Memanggil ${nama} dengan ${args}"
def hasil = target."$nama"(*args)
println "Hasil: ${hasil}"
hasil
}
}
class Layanan {
String sapa(String nama) {
"Halo, ${nama}"
}
}
def proxy = new LoggingProxy(target: new Layanan())
println proxy.sapa("Arman")proxy.sapa("Arman") triggers methodMissing, which logs, calls the real method with target."$nama"(*args), then returns the result. target."$nama"(*args) calls a dynamic method with the spread operator — the core of the dynamic proxy pattern.
import groovy.transform.Memoized
class Faktorial {
@Memoized
long hitung(int n) {
n <= 1 ? 1 : n * hitung(n - 1)
}
}
println new Faktorial().hitung(20)@Memoized is an AST transformation that wraps a method with a result cache. n <= 1 ? 1 : n * hitung(n - 1) leverages the conditional expression for recursion, and results are cached so repeated computation is avoided.
Episode 15 opened the world of Groovy metaprogramming: methodMissing and propertyMissing for dynamic responses, ExpandoMetaClass for extending classes at runtime, and a comparison of runtime and compile-time approaches through AST transformations.
The key takeaways:
methodMissing catches calls to undefined methods.propertyMissing catches access to nonexistent properties.ExpandoMetaClass extends both Java and Groovy classes at runtime.methodMissing and spread *args.@Memoized automatically adds method result caching.In episode 16 next, we'll discuss performance and optimization — evaluating the dynamic-versus-static trade-off, optimizing scripts with @CompileStatic, JVM profiling, and minimizing runtime overhead.