Release notes for Groovy 3.0

Note
WARNING: Material on this page is still under development! We are currently releasing beta versions of Groovy 3.0 with a goal of gathering feedback on the language changes from our community. In addition, the beta versions assist other projects and tool vendors within the Groovy ecosystem to begin assessing the impact of moving to/supporting Groovy 3.0. Caution should be exercised if using new features as the details may change before final release.

Parrot Parser

Groovy 3.0, has a new parser that is far more flexible and maintainable than the parser in previous versions of Groovy. It’s called the Parrot parser because in the early days of creating the parser, the goal was for the new parser’s output to be an exact echo of what the old parser produced. The new parser has since been extended to support additional syntax options and language features.

Disabling the parser

The new parser is enabled by default in Groovy 3.0. You can disable the new parser by using a system property. Use -Dgroovy.antlr4=false (set via JAVA_OPTS if needed). It is not envisaged that this property would be needed in normal use. However, at least initially, if you have a problematic source file that doesn’t seem to work with the new parser, you may be able to revert to the old parser to compile just that file. You won’t be able to use any of the new language features with the old parser. The old parser will be deprecated and eventually removed in future Groovy versions.

do/while loop

Java’s class do/while loop is now supported. Example:

// classic Java-style do..while loop
def count = 5
def fact = 1
do {
    fact *= count--
} while(count > 1)
assert fact == 120

Enhanced classic Java-style for loop

The more elaborate form of Java’s classic for loop with comma-separate expressions is now supported. Example:

def facts = []
def count = 5
for (int fact = 1, i = 1; i <= count; i++, fact *= i) {
    facts << fact
}
assert facts == [1, 2, 6, 24, 120]

Multi-assignment in combination with for loop

Groovy has supported multi-assignment statements since Groovy 1.6:

// multi-assignment with types
def (String x, int y) = ['foo', 42]
assert "$x $y" == 'foo 42'

These can now appear in for loops:

// multi-assignment goes loopy
def baNums = []
for (def (String u, int v) = ['bar', 42]; v < 45; u++, v++) {
    baNums << "$u $v"
}
assert baNums == ['bar 42', 'bas 43', 'bat 44']

Java-style array initialization

Groovy has always supported literal list/array definitions using square brackets and has avoided Java-style curly braces so as not to conflict with closure definitions. In the case where the curly braces come immediately after an array type declaration however, there is no ambiguity with closure definitions, so the Java style is now also supported.

Examples:

def primes = new int[] {2, 3, 5, 7, 11}
assert primes.size() == 5 && primes.sum() == 28
assert primes.class.name == '[I'

def pets = new String[] {'cat', 'dog'}
assert pets.size() == 2 && pets.sum() == 'catdog'
assert pets.class.name == '[Ljava.lang.String;'

// traditional Groovy alternative still supported
String[] groovyBooks = [ 'Groovy in Action', 'Making Java Groovy' ]
assert groovyBooks.every{ it.contains('Groovy') }

Java-style Lambda syntax

The Java syntax for lambda expressions is now supported.

Note
Experimental Status: While this feature will remain, we regard its current implementation as experimental. Currently, lambda expressions are turned into equivalent Groovy closures. An advantage of this approach is that it works even when using JDK7 (i.e. using Groovy 2.6 with Parrot parser enabled on a version 7 JRE) but it doesn’t easily enable the same level of type inference and checking that Java 8 offers even when using @TypeChecked or @CompileStatic and might be much less performant in a range of scenarios. We are currently exploring supporting native lambda expression either instead of, or in addition to, conversion to closures. If we change the implementation, most users would experience no behavioral differences in their programs though a native implementation would likely be faster but only work on JDK8+. Our goal is to clarify this aspect of the language before reaching release candidate status.

Examples:

import static java.util.stream.Collectors.toList

(1..10).forEach((it) -> { println it })

assert (1..10).stream()
        .filter((it) -> it % 2 == 0)
        .map((it) -> it * 2)
        .collect(toList()) == [4, 8, 12, 16, 20]

The normal variants are supported and Groovy adds additional features such as default parameter values:

// general form
def add = (int x, int y) -> { def z = y; return x + z }
assert add(3, 4) == 7

// curly braces are optional for a single expression
def sub = (int x, int y) -> x - y
assert sub(4, 3) == 1

// parameter types are optional
def mult = (x, y) -> x * y
assert mult(3, 4) == 12

// no parentheses required for a single parameter with no type
def isEven = n -> n % 2 == 0
assert isEven(6)
assert !isEven(7)

// no arguments case
def theAnswer = () -> 42
assert theAnswer() == 42

// any statement requires braces
def checkMath = () -> { assert 1 + 1 == 2 }
checkMath()

// example showing default parameter values (no Java equivalent)
def addWithDefault = (int x, int y = 100) -> x + y
assert addWithDefault(1, 200) == 201
assert addWithDefault(1) == 101

Method references

The Java 8 method reference syntax using the double colon syntax is now supported. Let’s first look at some of the supported cases before coming back to some implementation details.

The following examples illustrate referencing both static and instance methods of a class:

import java.util.stream.Stream
import static java.util.stream.Collectors.toList

// class::staticMethod
assert ['1', '2', '3'] ==
        Stream.of(1, 2, 3)
                .map(String::valueOf)
                .collect(toList())

// class::instanceMethod
assert ['A', 'B', 'C'] ==
        ['a', 'b', 'c'].stream()
                .map(String::toUpperCase)
                .collect(toList())

The following examples illustrate referencing methods of instance variables:

// instance::instanceMethod
def sizeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'::length
assert sizeAlphabet() == 26

// instance::staticMethod
def hexer = 42::toHexString
assert hexer(127) == '7f'

The following examples illustrate referencing constructors:

// normal constructor
def r = Random::new
assert r().nextInt(10) in 0..9

// array constructor refs are handy when working with various Java libraries, e.g. streams
assert [1, 2, 3].stream().toArray().class.name == '[Ljava.lang.Object;'
assert [1, 2, 3].stream().toArray(Integer[]::new).class.name == '[Ljava.lang.Integer;'

// works with multi-dimensional arrays too
def make2d = String[][]::new
def tictac = make2d(3, 3)
tictac[0] = ['X', 'O', 'X']
tictac[1] = ['X', 'X', 'O']
tictac[2] = ['O', 'X', 'O']
assert tictac*.join().join('\n') == '''
XOX
XXO
OXO
'''.trim()

// also useful for your own classes
import groovy.transform.Canonical
import java.util.stream.Collectors

@Canonical
class Animal {
    String kind
}

def a = Animal::new
assert a('lion').kind == 'lion'

def c = Animal
assert c::new('cat').kind == 'cat'

def pets = ['cat', 'dog'].stream().map(Animal::new)
def names = pets.map(Animal::toString).collect(Collectors.joining( "," ))
assert names == 'Animal(cat),Animal(dog)'

Implementation details and static optimization

While for the most part you can ignore implementation details, it is useful to understand the implementation behind method references in some scenarios. For dynamic Groovy, a method reference is implemented as a Closure method reference. So String::toUpperCase is the same as String.&toUpperCase. In the spirit of providing a more Java-like experience when using @CompileStatic, we support native method references for static Groovy.

For this example (using String.transform from JDK 12):

@groovy.transform.CompileStatic
def method() {
  assert 'Hi'.transform(String::toUpperCase) == 'HI'
}

The compiler will produce bytecode very similar to what Java would produce for this case (involves INVOKEDYNAMIC, method handles and LambdaMetafactory for the bytecode geeks). If you are already using @CompileStatic for extra compile-time type safety or performance, then the code will be semantically equivalent but optimized similar to Java.

If you have code making use of dynamic features, then you should not use @CompileStatic with your method references, e.g.:

def convertCase(boolean upper, String arg) {
    arg.transform(String::"${upper ? 'toUpperCase' : 'toLowerCase'}")
}
assert convertCase(true, 'Hi') == 'HI'
assert convertCase(false, 'Bye') == 'bye'

Since here the GString prohibits the compiler from knowing how to write the optimized code that would be required. Note: this example is a little contrived and could be refactored to call one of two optimized method references but hopefully you get the idea.

The same caveat applies if you want to make use of the Closure nature behind the dynamic implementation, e.g.:

def upper = String::toUpperCase
assert upper('hi') == 'HI'
def upperBye = upper.curry('bye')
assert upperBye() == 'BYE'

!in and !instanceof operators

When wanting the negated form, rather than having to bracket expressions containing the in and instanceof infix operators and placing the exclamation operator in front of the brackets, an inline variant is now also supported. Examples:

/* assert !(45 instanceof Date) // old form */
assert 45 !instanceof Date

assert 4 !in [1, 3, 5, 7]

Elvis assignment operator

Groovy introduced the Elvis operator Example:

import groovy.transform.ToString

@ToString
class Element {
    String name
    int atomicNumber
}

def he = new Element(name: 'Helium')
he.with {
    name = name ?: 'Hydrogen'   // existing Elvis operator
    atomicNumber ?= 2           // new Elvis assignment shorthand
}
assert he.toString() == 'Element(Helium, 2)'

Identity comparison operators

Both === and !== are supported which are the same as calling the is() method, and negating a call to the is() method respectively.

import groovy.transform.EqualsAndHashCode

@EqualsAndHashCode
class Creature { String type }

def cat = new Creature(type: 'cat')
def copyCat = cat
def lion = new Creature(type: 'cat')

assert cat.equals(lion) // Java logical equality
assert cat == lion      // Groovy shorthand operator

assert cat.is(copyCat)  // Groovy identity
assert cat === copyCat  // operator shorthand
assert cat !== lion     // negated operator shorthand

Safe indexing

String[] array = ['a', 'b']
assert 'b' == array?[1]      // get using normal array index
array?[1] = 'c'              // set using normal array index
assert 'c' == array?[1]

array = null
assert null == array?[1]     // return null for all index values
array?[1] = 'c'              // quietly ignore attempt to set value
assert null == array?[1]

def personInfo = [name: 'Daniel.Sun', location: 'Shanghai']
assert 'Daniel.Sun' == personInfo?['name']      // get using normal map index
personInfo?['name'] = 'sunlan'                  // set using normal map index
assert 'sunlan' == personInfo?['name']

personInfo = null
assert null == personInfo?['name']              // return null for all map values
personInfo?['name'] = 'sunlan'                  // quietly ignore attempt to set value
assert null == personInfo?['name']

ARM Try with resources

Groovy often provides better alternatives to Java 7’s try-with-resources statement for Automatic Resource Management (ARM). That syntax is now supported for Java programmers migrating to Groovy and still wanting to use the old style:

class FromResource extends ByteArrayInputStream {
    @Override
    void close() throws IOException {
        super.close()
        println "FromResource closing"
    }

    FromResource(String input) {
        super(input.toLowerCase().bytes)
    }
}

class ToResource extends ByteArrayOutputStream {
    @Override
    void close() throws IOException {
        super.close()
        println "ToResource closing"
    }
}

def wrestle(s) {
    try (
            FromResource from = new FromResource(s)
            ToResource to = new ToResource()
    ) {
        to << from
        return to.toString()
    }
}

assert wrestle("ARM was here!").contains('arm')

Which yields the following output:

ToResource closing
FromResource closing

Nested code blocks

An infrequently used structure within Java is the anonymous code block. It’s generally not encouraged as it’s often a sign that refactoring the related code into a method is in order. But it’s sometimes useful to restrict scoping and is now available in Groovy:

{
    def a = 1
    a++
    assert 2 == a
}
try {
    a++ // not defined at this point
} catch(MissingPropertyException ex) {
    println ex.message
}
{
    {
        // inner nesting is another scope
        def a = 'banana'
        assert a.size() == 6
    }
    def a = 1
    assert a == 1
}

Be aware though that in Groovy having a code block looking structure after any method call will be seen as an attempt to pass a closure as the last parameter in the method call. This happens even after a new line. So it’s safe to start an anonymous code block after any other block (e.g. an if-then-else statement or another anonymous code block). Anywhere else and you might need to terminate the previous statement with a semicolon. In which case, see the note above about refactoring your code! :-)

Java-style non-static inner class instantiation

Java syntax for non-static inner class instantiation is now supported.

public class Computer {
    public class Cpu {
        int coreNumber

        public Cpu(int coreNumber) {
            this.coreNumber = coreNumber
        }
    }
}

assert 4 == new Computer().new Cpu(4).coreNumber

Interface default methods

Java 8 supports adding default implementations to interfaces. Groovy now supports this too:

interface Greetable {
    String target()

    default String salutation() {
        'Greetings'
    }

    default String greet() {
        "${salutation()}, ${target()}"
    }
}

class Greetee implements Greetable {
    String name
    @Override
    String target() { name }
}

def daniel = new Greetee(name: 'Daniel')
assert 'Greetings, Daniel' == "${daniel.salutation()}, ${daniel.target()}"
assert 'Greetings, Daniel' == daniel.greet()
Note
Experimental Status: While this feature will remain, we regard its current implementation as experimental. Currently, interface default methods are implemented using traits. This means that they work even when using JDK7 (i.e. using Groovy 2.6 with Parrot parser enabled on a version 7 JRE). We are currently exploring supporting native default methods in interfaces instead of, or in addition to, the trait implementation. If we change the implementation, we would expect no change in logical behavior though the new implementation may have different performance characteristics in some circumstances and the native implementation would only work on JDK8+. Our goal is to clarify this aspect of the language before reaching release candidate status.

Split package changes (from beta-2)

The Java Platform Module System requires that classes in distinct modules have distinct package names. Groovy has it’s own "modules" but these haven’t historically been structured according to the above requirement. For this reason, Groovy 2.x and 3.0 should be added to the classpath not module path when using JDK9+. This places Groovy’s classes into the unnamed module where the split package naming requirement is not enforced.

Groovy 3 is making changes to allow the codebase to move towards the compliant rules and allow Groovy users to begin the migration process. Groovy 4 is our target version for fully-compliant artifacts but you can start getting your classes ready ahead of time while using Groovy 3.

As part of this change, some classes are moving packages. In a subset of those moved classes, it makes sense for Groovy 3 to have two copies of such classes:

  • a deprecated class having the old package name

  • a new class having the new package name

This can aid with migration. In many cases, you will be able to recompile existing Groovy classes without change and they will use the old versions of the classes. You may notice deprecation warnings depending on how you edit classes. You should migrate as soon as convenient since your classes may no longer compile under Groovy 4 if you haven’t migrated to the new class location. Be aware that in some cases, some work is required even under Groovy 3. Read the Notes column in the table below for further details.

Original class/package name (3.0 and lower if applicable)

New copied class/package name
(3.0 and above)

Notes

Module: groovy

groovy.xml.QName

groovy.namespace

You need to migrate to using the new class at the same time as you migrate to affected modules using that class as a parameter in methods including groovy-ant and groovy-xml but only if you are using methods with QName parameters. You can continue to use the legacy class in your existing code or with the legacy versions of affected classes until Groovy 4.

Module: groovy-ant

groovy.util

groovy.ant

Add an import groovy.ant.AntBuilder to classes/scripts using AntBuilder or you will still be using the deprecated version.

Module: groovy-console

groovy.ui.ConsoleApplet

N/A

The java.applet API is deprecated. No replacement is planned for this Groovy class in Groovy 4.

groovy.inspect

groovy.console

groovyConsole is normally used as a command-line tool and its use in that form is unaffected. If you use any of the classes directly you can use the old versions until you migrate. You should not mix and match old and new classes.

groovy.inspect.swingui

groovy.console.ui

groovy.ui

groovy.console.ui

Module: groovy-groovysh

org.codehaus.groovy.tools.shell

org.apache.groovy.groovysh

groovysh is normally used as a command-line tool and its use in that form is unaffected. If you use any of the classes directly you can use the old versions until you migrate. You should not mix and match old and new classes.

Module: groovy-jmx

groovy.util.GroovyMBean

groovy.jmx

You need to add the import for GroovyMBean before Groovy 4. Feel free to use the old class in your own code but JmxBuilder only uses the new class. You should not mix the old and new classes.

Module: groovy-nio

org.codehaus.groovy.runtime.
NioGroovyMethods

org.apache.groovy.nio.extensions.
NioExtensions

In normal use, related extension methods will simply be automatically available from the new location.

org.codehaus.groovy.runtime.
WritablePath

org.apache.groovy.nio.runtime

We recommend that you reference WritablePath via its interfaces in which case you have nothing to do. If you must reference the class, we recommend changing imports and recompiling all affected classes. If this is difficult, you can use the old class (and directly use the related NioGroovyMethods methods) until you are ready to migrate. You should not mix and match old and new classes.

Module: groovy-swing

org.codehaus.groovy.binding

org.apache.groovy.swing.binding

You can continue to use the old classes if you use them in your existing code or from within legacy classes still using the old classes. SwingBuilder now uses the new classes.

groovy.model

groovy.swing.model

groovy.inspect.swingui

org.apache.groovy.swing.table

Module: groovy-test

org.codehaus.groovy.runtime.
ScriptTestAdapter

org.apache.groovy.test

The old class remains available for use in your own classes if already in use but won’t be recognized by Groovy 3’s JUnit-related test suite classes.

groovy.transform.
NotYetImplemented

groovy.test.
NotYetImplemented

Both point to the (moved but otherwise unchanged) AST transform class.

groovy.util

groovy.test

For classes like GroovyTestCase, you need to import groovy.test.GroovyTestCase to not get the deprecated version. You will need to do this before Groovy 4.

groovy.lang

groovy.test

Module: groovy-xml

groovy.util

groovy.xml

For classes like XmlParser and XmlSlurper, you need to import groovy.xml.XmlParser and groovy.xml.XmlSlurper respectively to not get the deprecated versions. You will need to do this before Groovy 4. If you are using groovy.xml.XmlUtil.serialize on a groovy.util.slurpersupport.GPathResult, you will need to swap to using the deprecated methods in groovy.util.XmlUtil since groovy.xml.XmlUtil only handles the new classes.

org.codehaus.groovy.tools.xml.DomToGroovy

org.apache.groovy.xml.tools

Miscellaneous improvements

Embedded Groovydoc

You can now embed Groovydoc comments in various ways:

  • They can be made available within the AST for use by AST transformations and other tools. Our revamped groovydoc tool (still under development) is based on this capability. Behind the scenes the groovydoc content is stored as node metadata but a simple API hides this implementation detail. This feature is enabled using the groovy.attach.groovydoc system property or corresponding flag in CompilerConfiguration.

  • Groovydoc comments starting with a special /**@ opening comment delimiter can also be embedded into the class file (behind the scenes it’s stored in a @Groovydoc annotation) and is available at runtime for access via reflection or via other tools. This is enabled using the groovy.attach.runtime.groovydoc system property or corresponding flag in CompilerConfiguration. This provides a capability in Groovy inspired by languages like Ruby which can embed documentation into the standard binary jar and is thus always available rather than relying on a separate javadoc jar.

Here is an example illustrating access to groovydoc comments within the AST:

import org.codehaus.groovy.control.*

def cc = new CompilerConfiguration(optimizationOptions:
    [(CompilerConfiguration.GROOVYDOC): true])

def ast = new CompilationUnit(cc).tap {
    addSource 'myScript.groovy', '''
        /** class doco */
        class MyClass {
            /** method doco */
            def myMethod() {}
        }
    '''
    compile Phases.SEMANTIC_ANALYSIS
}.ast

def classDoc = ast.classes[0].groovydoc
assert classDoc.content.contains('class doco')
def methodDoc = ast.classes[0].methods[0].groovydoc
assert methodDoc.content.contains('method doco')

Here is an example using illustrating runtime groovydoc (with and without the flag set):

import org.codehaus.groovy.control.*

def extract(shell) {
    shell.evaluate( '''
        /**@
         * Some class groovydoc for Foo
         */
        class Foo {}
        Foo.class
        '''
    ).groovydoc.content.replaceAll('[^\\w\\s]', '').trim()
}

// first without the flag set
assert extract(new GroovyShell()) == ''

// now with embedding turned on
def cc = new CompilerConfiguration(optimizationOptions:
    [(CompilerConfiguration.RUNTIME_GROOVYDOC): true])
assert extract(new GroovyShell(cc)) == 'Some class groovydoc for Foo'

JSR308 improvements (work in progress)

Groovy has been improving JSR-308 support over recent versions. As part of implementing the new grammar, additional support has been added.

JDK requirements

Groovy 3.0 requires JDK9+ to build and JDK8 is the minimum version of the JRE that we support.

More information

You can browse all the tickets closed for Groovy 3.0 in JIRA. Note: some of the tickets relevant for this release are contained in the tickets closed for Groovy 2.6.