Version 4 of the programming language was released two years after Apache Groovy 3.0. The fresh major release of the JVM (Java Virtual Machine) language introduces some of the Java language features of recent years, albeit with slight variations. It also gets its own query language with GINQ and relies on design by contract.
Beyond the linguistic innovations, the release says goodbye to the legacy packages that existed as duplicates of the packages newly introduced in Groovy 3 for interaction with the Java Platform Module System (JPMS). The coexistence should ensure a smooth transition to the new conventions. Details on the old and new names can be found in the release notes for version 3.0.
One variable, many cases
Java introduced switch expressions in version 13 as a supplement to switch statements and stabilized them in Java 14. The variant of the language construct that has now been implemented in Groovy is based on the Java procedure. While switch statements for each case case
with a :
and subsequent code, switch expressions can, among other things, directly assign a value to a variable. After case
usually stands in each case ->
. Many typical switch
-Blocks are now clearer:
// Switch Statement
def result
switch(i)
case 0: result = 'Null'; break
case 1: result = 'Eins'; break
case 2: result = 'Zwei'; break
default: throw new IllegalStateException('unbekannt')
// Switch Expression
def result = switch(i)
case 0 -> 'Null'
case 1 -> 'Eins'
case 2 -> 'Zwei'
default -> throw new IllegalStateException('unbekannt')
Behind ->
must be exactly a single expression. However, multiple statements can be combined in a block enclosed in curly brackets. It is also possible to program switch expressions in the old colon notation. Every case has to have one yield
included to assign the value to the variable. Mixing the two forms with :
and ->
is not allowed.
In contrast to Java, the current implementation does not have to cover all cases. If no case
-Row reflects the value and no default
exists, Groovy implicitly adds null
added as a default. However, there are probably already strict requirements for mandatory coverage of all cases and the avoidance of null
in planning.
Sealed Types
Sealed classes have existed in the Java world since version 15 and have been considered stable since Java 17. Groovy also now allows classes, interfaces, and traits to be sealed to specify which types are eligible as children. Sealing is done using the keyword sealed
or the annotation @Sealed
. The permitted child types can be specified explicitly permits
respectively permittedSubclasses
determine. In addition, the compiler implicitly allows the subtypes defined in the same file.
In interaction with Java from the current version 17, Groovy creates the bytecode as sealed classes that are compatible with those in Java. For older Java variants, the Groovy compiler recognizes the sealed types, but the Java compiler treats them as regular ones. In Groovy 4.0, sealed classes are marked as incubating. This means that changes to the concrete implementation are potentially possible.
Immutable Content
Records made their debut in Java 14 and have been considered stable since Java 16. They serve as an object-oriented construct for storing simple values in the form of immutable data, i.e. unchangeable data. Groovy has known about annotation for quite some time @Immutable
for immutable content.
With the current release, Groovy leads the keyword record
and the annotation @RecordType
which are implemented as native records in interaction with Java from version 16. As with the sealed types, an emulated implementation applies to earlier Java variants, which behave in the same way, but which the Java compiler does not recognize as records.
Query about collections
Another innovation marked as incubating is the query language Groovy-Integrated Query (GINQ). It can be used to create queries on collections in a similar way to SQL, as the following code example from the release notes shows:
from p in persons
leftjoin c in cities on p.city.name == c.name
where c.name == 'Shanghai'
select p.name, c.name as cityName
from p in persons
groupby p.gender
having p.gender == 'Male'
select p.gender, max(p.age)
from p in persons
orderby p.age in desc, p.name
select p.name
from n in numbers
where n > 0 && n <= 3
select n * 2
from n1 in nums1
innerjoin n2 in nums2 on n1 == n2
select n1 + 1, n2
The Groovy documentation shows further examples in addition to the description of GINQ.
Under contract
The module is also new and incubating groovy.contracts
, which introduces design-by-contract (DbC) programming. The procedure comes from the Eiffel programming language and is based on a type of contract for using the interfaces that are intended to ensure the interaction of the individual program modules. For C++, contracts were actually planned for C++20, but it is now questionable whether they will at least make it into the upcoming C++23.
The contracts in the DbC specify the pre-conditions and the post-conditions. The invoker must observe the former and the invoked the latter. As the third building block, the invariants describe conditions that must always apply when executing. Groovy provides its own annotations for the three elements. An implementation can be found in the following code example from the release notes:
import groovy.contracts.*
@Invariant( speed() >= 0 )
class Rocket
int speed = 0
boolean started = true
@Requires( isStarted() )
@Ensures( old.speed < speed )
def accelerate(inc) speed += inc
def isStarted() started
def speed() speed
def r = new Rocket()
r.accelerate(5)
An external project for design by contract, GContracts, has existed for Groovy for a long time, which is not being further developed and is archived.
Further innovations in Groovy 4 such as the JavaShell and POJO annotations (Plain Old Java Object) for a leaner implementation of static Groovy objects can be found in the release notes. The download page refers to the source code and binaries for different operating systems.
(rm)