Skip to content
AgileLogics
5 min read

Categories and Extension Modules

Prerequisites: Runtime metaprogramming

Two safer ways to add methods to existing classes.

Categories — methods in a scope

A category is a class of static methods. Inside a use block, the first parameter becomes the receiver:

class StringExtras {
    static String slugify(String self) {
        self.toLowerCase().replaceAll(/[^a-z0-9]+/, '-').replaceAll(/^-|-$/, '')
    }
    static int wordCount(String self) { self.tokenize().size() }
}

use(StringExtras) {
    println 'Hello, Groovy World!'.slugify()
    println 'one two three'.wordCount()
}

try {
    'outside'.slugify()
} catch (MissingMethodException e) {
    println 'slugify() only exists inside use { }'
}
hello-groovy-world
3
slugify() only exists inside use { }

Changes are visible only inside the use block (and only on that thread), so they can't leak into other code.

TimeCategory

Groovy ships a category for readable durations and dates:

import groovy.time.TimeCategory

use(TimeCategory) {
    def meeting = 1.hour + 30.minutes
    println meeting
    println 90.minutes
    def start = Date.parse('yyyy-MM-dd HH:mm', '2026-09-29 09:00')
    println((start + 2.days + 3.hours).format('yyyy-MM-dd HH:mm'))
}
1 hours, 30 minutes
90 minutes
2026-10-01 12:00

Extension modules — permanent additions, done properly

An extension module is how Groovy itself adds methods to JDK classes, and how libraries ship their own. You write static methods in a normal class and register them in a descriptor file on the classpath:

// illustrative — src/main/groovy/com/example/MoneyExtension.groovy
package com.example

class MoneyExtension {
    static BigDecimal withVat(BigDecimal self, BigDecimal rate = 0.2) {
        self * (1 + rate)
    }
    static String asEuro(Number self) {
        String.format(Locale.UK, '€%,.2f', self)
    }
}
# src/main/resources/META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule
moduleName=money-extensions
moduleVersion=1.0
extensionClasses=com.example.MoneyExtension

Once the jar is on the classpath, every Groovy script and class can call 100.0.withVat() or 1234.5.asEuro() — and, unlike metaclass changes, extension methods are also seen by @CompileStatic code and by IDEs.

Choosing a technique

Technique Scope Static compilation Typical use
metaClass global, runtime no scripts, tests, frameworks
Category (use) a block, one thread no temporary DSL vocabulary
Extension module everywhere the jar is yes libraries, team utilities
Trait classes that implement it yes your own types