Extension Modules
|
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). An extension module is a jar that adds methods to existing classes, the way the GDK adds each, collect
or toURL() to JDK types, but for your own classes of helpers. The methods are written once as plain static methods, announced in
a small descriptor file inside the jar, and are then available on every user of the jar, in scripts and in compiled code, and
(unlike ExpandoMetaClass additions and categories, see
Metaclasses, ExpandoMetaClass & Categories) are
understood by the static type checker and by @CompileStatic. This is the mechanism to prefer for library-quality additions to
classes you do not own. The runtime hooks behind it are described on
Runtime Metaprogramming & the MOP.
An extension module has three parts:
-
One or more extension classes with static methods whose first parameter is the receiver (instance extensions), and optionally static extension classes whose first parameter only names the target class (static extensions).
-
The module descriptor
META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule, a properties file listing those classes. -
The classes and the descriptor packaged together, typically in a jar on the classpath.
Writing extension methods
For an instance extension method, write a static method whose first parameter has the type you are extending. A call
receiver.method(args) runs Extension.method(receiver, args). The method name is the one callers will use. A static method named
isX or getX with only the receiver parameter also gives a property x.
For a static extension method, write a static method in a separate class registered as a static extension class. The first
parameter is used only to name the class that receives the method (a call String.greenhouse() runs
GreenhouseStaticExtensions.greenhouse(String)); its value is not meaningful.
The two classes below add temperature conversion and a frost check to numbers, a shout() to strings, and two static helpers. They
were compiled with groovyc into a scratch directory.
// src/greenhouse/GreenhouseExtensions.groovy
package greenhouse
class GreenhouseExtensions {
// instance extensions: the first parameter is the receiver
static double toFahrenheit(Number self) { self * 9 / 5 + 32 }
static String shout(String self) { self.toUpperCase() + '!' }
static boolean isFrostRisk(Number self) { self < 2 }
}
// src/greenhouse/GreenhouseStaticExtensions.groovy
package greenhouse
class GreenhouseStaticExtensions {
// static extensions: the first parameter only names the target class
static String greenhouse(String self) { 'greenhouse' }
static List<String> zones(Integer self) { ['north', 'south'] }
}
A generic extension such as static <T> List<T> second(Iterable<T> self) works too (it is used under "Precedence and conflicts").
The module descriptor
Groovy discovers modules through a file named META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule, found on the classpath
(each jar may carry one). It is a properties file with four keys:
| Key | Meaning |
|---|---|
|
A name for the module; used to detect conflicts. |
|
The module’s version; used to detect conflicts. |
|
Comma-separated, fully qualified names of the classes holding instance extension methods. |
|
Comma-separated, fully qualified names of the classes holding static extension methods. |
moduleName=greenhouse-extensions
moduleVersion=1.0
extensionClasses=greenhouse.GreenhouseExtensions
staticExtensionClasses=greenhouse.GreenhouseStaticExtensions
Either class list can be omitted if the module has no extensions of that kind.
Packaging
The jar needs the compiled classes and the descriptor at the paths above. This is the whole build used for this page, done with
the groovyc and jar command line tools; a Maven or Gradle project does the same by putting the descriptor under
src/main/resources/META-INF/groovy/ and publishing the normal jar (not run here).
groovyc -d classes src/greenhouse/*.groovy
mkdir stage
cp -R classes/. stage/
cp -R res/META-INF stage/
(cd stage && jar cf ../greenhouse-extensions-1.0.jar .)
jar tf greenhouse-extensions-1.0.jar
META-INF/
META-INF/MANIFEST.MF
META-INF/groovy/
META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule
greenhouse/
greenhouse/GreenhouseExtensions.class
greenhouse/GreenhouseStaticExtensions.class
Using the module
Add the jar to the classpath and the methods are there. Nothing is imported; the module is global for that classpath.
// use1.groovy
println 25.toFahrenheit()
println 'basil'.shout()
println 1.isFrostRisk()
println String.greenhouse()
println Integer.zones()
println 25.metaClass.respondsTo(25, 'toFahrenheit').size()
groovy -cp greenhouse-extensions-1.0.jar use1.groovy
77.0
BASIL!
true
greenhouse
[north, south]
1
Without the jar the same script fails: groovy use1.groovy ends with MissingMethodException: No signature of method:
toFahrenheit for class: java.lang.Integer. Extension methods are reported by respondsTo, so they are discoverable at run time.
In a Gradle or Maven build the module is simply a dependency (not run here).
Static type checking support
Because the compiler reads the descriptor from the classpath, an extension module is visible to @TypeChecked and
@CompileStatic code. That is the main reason to choose it over a metaclass addition. Compiled with the jar on the classpath, this
class type-checks and runs; the property form 1.frostRisk comes from the isFrostRisk extension.
// use2.groovy
import groovy.transform.CompileStatic
@CompileStatic
class Report {
static String run() {
double f = 25.toFahrenheit()
String s = 'basil'.shout()
"${f} ${s} ${String.greenhouse()} ${1.frostRisk}"
}
}
println Report.run()
groovy -cp greenhouse-extensions-1.0.jar use2.groovy
77.0 BASIL! greenhouse true
Without the jar the compiler rejects the call at compile time:
bad.groovy: 4: [Static type checking] - Cannot find matching method java.lang.String#shout(). Please check if the declared type is correct and if the method exists.
@ line 4, column 27.
static String run() { 'basil'.shout() }
The same applies to type checking extensions; for methods that must exist only at run time and are not in a module, see Type Checking Extensions.
Precedence and conflicts
Two rules were verified on Groovy 6.0.0 with a second module, clash-1.0.jar, which declares static int length(String self)
(returning -1), reverseWords(String) and a generic second(Iterable):
// use3.groovy
println 'abc'.length()
println 'north south east'.reverseWords()
println([5, 6, 7].second())
println(([1: 'a', 2: 'b'] as LinkedHashMap).keySet().second())
groovy -cp clash-1.0.jar use3.groovy
3
east south north
[6]
[2]
-
A method that the class already has wins over an extension method of the same signature:
'abc'.length()still returns 3 although the module defineslength(String). This differs from categories, where the category method wins. An extension can add overloads and new names, but it cannot replace existing methods; use a metaclass or a category for that. -
Two jars with the same
moduleNamebut differentmoduleVersion`s cannot be loaded together.Putting `greenhouse-extensions-1.0.jar and a2.0build of the same module on one classpath fails at startup withConflicting module versions. Module [greenhouse-extensions is loaded in version 1.0 and you are trying to load version 2.0. Keep one version of each module on the classpath, and give independent modules distinct names.
How the GDK relates
The GDK is built from the same kind of classes. The core ones (DefaultGroovyMethods and its siblings) are loaded by the
runtime itself, and Groovy’s optional modules ship a descriptor exactly like the one above. In the 6.0.0 distribution,
groovy-nio-6.0.0.jar contains META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule with
moduleName=groovy-nio
moduleVersion=6.0.0
extensionClasses=org.apache.groovy.nio.extensions.NioExtensions
staticExtensionClasses=
and the groovy-sql, groovy-xml, groovy-datetime, groovy-dateutil, groovy-swing, groovy-ginq, groovy-jsr223 and groovy-macro
jars carry one too. This is why text or eachLine show up on Path and File without JDK changes, and why type checking knows
about them. See GDK Overview & Object Methods.
Where the book is outdated
Older books describe adding methods to existing classes only through ExpandoMetaClass and categories, and mention extension modules
briefly or not at all; extension modules arrived in Groovy 2.0 and are the approach the project itself uses for the GDK. The descriptor format shown here is
the one documented for Groovy 6.0.