Release notes for Groovy 5.0

Groovy 5 builds upon existing features of earlier versions of Groovy. In addition, it incorporates numerous new features and streamlines various legacy aspects of the Groovy codebase.

Note
WARNING: Material on this page is still under development! We are currently working on alpha versions of Groovy 5.0 with a goal of gathering feedback on the language changes from our community. In addition, early versions assist other projects and tool vendors within the Groovy ecosystem to begin assessing the impact of moving to/supporting Groovy 5.0. Caution should be exercised if using new features as the details may change before final release. Some features described here as "incubating" may become stable before 5.0.0 final is released, others are expected to remain incubating for version 5. We don’t recommend using alpha versions or incubating features for production systems.

New features

Extension method additions and improvements

There are some additional extension methods for File objects:

def myscript = new File('MyScript.groovy')
assert myscript     // Groovy truth: true if the file exists
assert myscript.extension == 'groovy'
assert myscript.baseName == 'MyScript'

And similar methods for Path objects:

def mypic = path.resolve('MyFigure.png')
assert mypic       // Groovy truth: true if the file exists
assert mypic.extension == 'png'
assert mypic.baseName == 'MyFigure'

There are additional variants of collectEntries for arrays, iterables and iterators with separate functions for transforming the keys and values. There are variants with and without collectors. There are also variants which transform just the key or value. The withCollectedKeys method collects key/value pairs for each item with the item as the value and the key being the item transformed by the supplied function. The withCollectedValues method collects key/value pairs for each item with the item as the key and the value being the item transformed by the supplied function.

def languages = ['Groovy', 'Java', 'Kotlin', 'Scala']

def collector = [clojure:7]
assert languages.collectEntries(collector, String::toLowerCase, String::size) ==
    [clojure:7, groovy:6, java:4, kotlin:6, scala:5]
assert languages.withCollectedKeys(s -> s.take(1)) ==
    [G:'Groovy', J:'Java', K:'Kotlin', S:'Scala']
assert languages.withCollectedValues(s -> s.size()) ==
    [Groovy:6, Java:4, Kotlin:6, Scala:5]

There are also equivalent variants for maps. The collectEntries method takes separate functions for transforming the keys and values. The collectKeys and collectValues variants take a single function for transforming just the keys and values respectively.

def lengths = [Groovy:6, Java:4, Kotlin:6, Scala:5]

assert lengths.collectEntries(String::toLowerCase, { it ** 2 }) ==
    [groovy:36, java:16, kotlin:36, scala:25]
assert lengths.collectKeys{ it[0] } == [G:6, J:4, K:6, S:5]
assert lengths.collectValues(Math.&pow.rcurry(2)) ==
    [Groovy:36.0, Java:16.0, Kotlin:36.0, Scala:25.0]
assert lengths.collectValues(Math.&pow.curry(2).memoize()) ==
    [Groovy:64.0, Java:16.0, Kotlin:64.0, Scala:32.0]

Other improvements

Ongoing work

Enhanced switch (under investigation)

Groovy has always had a very powerful switch statement. The statement could be made more powerful, e.g. support destructuring, and could be supported in contexts where expressions are expected.

As inspiration, Java has made, or is investigating future enhancements including switch expressions and other related enhancements: JEP 354: Switch Expressions (Second Preview) JEP 361: Switch Expressions JEP 405: Record Patterns & Array Patterns (Preview) JEP 406: Pattern Matching for switch (Preview) We should investigate these proposals both in terms of enhancing the existing Groovy switch but also in terms of deciding which syntax from Java we might like to support in the future.

Other languages like Python are also improving their switch statements: PEP 622 — Structural Pattern Matching. We should investigate whether any features of their design make sense for Groovy’s dynamic nature.

As an example of destructuring, instead of the following existing code:

def make3D(pt) {
    switch(pt) {
        case Point3D:
            return pt
        case Point2D:
            return new Point3D(pt.x, pt.y, 0)
        case List:
            def (x, y, z) = pt
            if (x == 0 && y == 0 && z == 0)
                throw new IllegalArgumentException("Origin not allowed")
            return new Point3D(x, y, z)
            ...
    }
}

You could use something like:

def make3D(pt) {
    switch(pt) {
        case Point3D:
            return pt
        case Point2D(x, y):
            return new Point3D(x, y, 0)
        case [0, 0, 0]:
            throw new IllegalArgumentException("Origin not allowed")
        case [x, y, z]:
            return new Point3D(x, y, z)
            ...
    }
}

An example of guarded patterns being considered for Java:

static void testTriangle(Shape s) {
    switch (s) {
        case null ->
            System.out.println("Null!");
        case Triangle t && (t.calculateArea() > 100) ->
            System.out.println("Large triangle");
        case Triangle t ->
            System.out.println("Small triangle");
        default ->
            System.out.println("Non-triangle");
    }
}

Another destructuring example:

int eval(Expr n) {
     return switch(n) {
         case IntExpr(int i) -> i;
         case NegExpr(Expr n) -> -eval(n);
         case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
         case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
         default -> throw new IllegalStateException();
     };
}

We should consider the currently proposed nested record pattern when exploring our destructuring options, e.g.:

static void printColorOfUpperLeftPoint(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
        System.out.println(c);
    }
}

Other Java-inspired enhancements

  • Module definitions written in Groovy (i.e. module-info.groovy) GROOVY-9273

  • Use of "_" (underscore) for unused parameters (see "Treatment of underscores" in JEP 302: Lambda Leftovers)

Other breaking changes

  • Numerous classes previously "leaked" ASM constants which are essentially an internal implementation detail by virtue of implementing an Opcodes interface. This will not normally affect the majority of Groovy scripts but might impact code which manipulates AST nodes such as AST transforms. Before compiling with Groovy 4, some of these may need one or more appropriate static import statements added. AST transforms which extend AbstractASTTransformation are one example of potentially affected classes. (GROOVY-9736).

JDK requirements

Groovy 5.0 requires JDK16+ to build and JDK8 is the minimum version of the JRE that we support. This may change before the GA version of Groovy 5 is released. Groovy has been tested on JDK versions 8 through 17.

More information

You can browse all the tickets closed for Groovy 5.0 in JIRA.