What’s New & Migrating from Older Versions

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 collects in one place what changed between Groovy releases and how to move code forward. The baseline is Groovy 6.0.0 (released 2026-09-22, JDK 17 or newer); every example on the page was run with Groovy 6.0.0 on JDK 26 on 2026-10-14. Version numbers, dates and maintained lines were checked on that day against the official change log and the release notes. Features introduced after Groovy 4.0 name their release, and a feature described as incubating may still change in a minor release. Each change is explained in depth on the guide page linked from it.

Release by release: 1.0 to 6.0

Groovy follows semantic versioning. Two release numbers never shipped under their original name: 1.1 was renamed 1.5 and 1.9 was renamed 2.0. A 2.6 line was planned as a JDK 7 back-port of the 3.0 parser but was retired after four alpha releases (the last on 2018-06-23), so it is not in the table. A 7.0 release-notes page exists but is marked as under development with no release on the change log; nothing on this page depends on it.

Release Date JDK Headline features and notes

1.0

2007-01-03

old JDKs (not stated in the notes)

The language that the first generation of Groovy books describes. There is no release-notes page for 1.0; the change log lists the release.

1.5

2007-12-07

old JDKs (not stated in the notes)

Java 5 features: annotations, enums, generics, static imports, varargs, covariant returns; the classic for loop, the Elvis operator and named parameters without parentheses; the joint compiler, the Maven plugin, GroovyDoc, a new shell and Swing console; richer dynamic behaviour such as ExpandoMetaClass with its methodMissing and propertyMissing hooks; a more capable SwingBuilder.

1.6

2009-02-18

old JDKs (not stated in the notes)

Compile-time AST transformations (@Singleton, @Immutable, @Lazy, @Delegate, @Newify, @Category, @Mixin, @PackageScope); Grape and @Grab; multiple assignment; annotation definitions; optional return in if/else and try; per-instance metaclasses and runtime mixins; JSR 223 scripting engine; JMX builder; better OSGi support.

1.7

2009-12-22

old JDKs (not stated in the notes)

Anonymous inner classes and nested classes; annotation improvements; power asserts; a rewritten GroovyScriptEngine; a customisable Groovy truth; an AST viewer in the Swing console and the AST builder; SQL batch updates and transactions; IDE support.

1.8

2011-04-27

old JDKs (not stated in the notes)

Command chains for DSLs; closure composition, trampoline (stack-safe recursion), currying and memoisation improvements; GPars bundled; native JSON support; more AST transformations (@Log, @Field, @ToString, @EqualsAndHashCode, @TupleConstructor, @Canonical, @InheritConstructors, @WithReadLock); the diamond operator; slashy and dollar-slashy strings; compilation customizers.

2.0

2012-06-28

JDK 7 features need JDK 7

Static type checking and static compilation (@TypeChecked, @CompileStatic); Project Coin syntax; invokedynamic support; the core split into modules; extension modules.

2.1

2013-01-24

JDK 7 for indy

Full invokedynamic support; GPars 1.0; @DelegatesTo; type checker extensions; compile-time meta-annotations; compiler configuration scripts and the customizer builder.

2.2

2013-11-18

old JDKs (not stated in the notes)

Implicit closure coercion to single-method interfaces; @Memoized; base-script-class annotation and DelegatingScript; a Log4j2 variant of @Log; precompiled type checking extensions; groovysh enhancements.

2.3

2014-05-05

runs on JDK 8

Traits; a NIO module; much faster JSON parsing and serialisation; closure parameter type inference; the markup template engine; GroovyAssert for JUnit 4.

2.4

2015-01-21

old JDKs (not stated in the notes)

Android support; smaller bytecode and better performance; @SelfType for traits. The 2.4 line is also where the project moved to the Apache Software Foundation (incubator 2015, top-level project the same year).

2.5

2018-05-27

JRE 7+, build on JDK 8

Macros; @AutoFinal, @AutoImplement, @MapConstructor, @NamedVariant, @VisibilityOptions; JDK 9 support with a modular jar layout (groovy-all becomes a POM); a rewritten CliBuilder. The last release of the 2.x series.

3.0

2020-02-07

JRE 8+, build on JDK 9

The Parrot parser: lambdas, method references, do/while, the enhanced classic for, Java array initialisers, !in, !instanceof, ===, ?=, safe indexing, var, try-with-resources, nested blocks, default interface methods; the JPMS-friendly packages that duplicate the old ones.

4.0

2022-01-25

JRE 8+, tested 8-17

org.apache.groovy coordinates; the legacy packages and the old parser removed; invokedynamic on by default; switch expressions; sealed types, records, JavaShell, @POJO, groovy-contracts, GINQ and TOML (all incubating); built-in type checkers and macro methods.

5.0

2025-08-21

JRE 11+, build on JDK 17, tested 11-25

About 350 new GDK methods; JEP 512 instance main scripts; Jakarta EE support; FormatStringChecker; @OperatorRename; a rewritten groovysh on JLine 3; native interface default, private and static methods; instanceof patterns; the _ placeholder; the java.time default import; ==>; var with multiple assignment; a loop index variable; named groups with =~.

5.1

2026-08-12

JRE 11+, tested 11-25 (EA 26 and 27)

Console support on JDK 26 and later; security hardening: safer recursive directory deletion, stricter Grape coordinates, @ASTTest can be disabled.

6.0

2026-09-22

JRE 17+, tested 17-27

Async/await and the groovy.concurrent toolkit (incubating); val; module imports; rest destructuring; compound assignment overloading; intersection-type casts for lambdas, method references and closures; AST transforms on loop statements (incubating); the NullChecker type checker and the contract checkers (ModifiesChecker, PurityChecker, CombinerChecker); @SafeRegex regex timeouts; groovyc --error-format short; nested copyWith; the switch position rule; sealed types, TOML, GINQ, contracts, macros and JavaShell leave incubation; new modules (groovy-http-builder, groovy-csv, groovy-markdown, groovy-grape-maven, groovy-test-junit6, groovy-callsite); secure-by-default XML; GroovyDoc Markdown comments; Security Manager support removed.

The rows for 1.5 to 2.5 summarise the corresponding release notes; the notes of the older lines do not state a minimum JDK, so the column says so instead of guessing. Source: Groovy documentation: Release notes and Change logs.

Maintained lines on 2026-10-14

Line Latest release on the change log Minimum JRE

6.0

6.0.0 (2026-09-22)

17

5.1

5.1.3 (2026-09-19)

11

5.0

5.0.8 (2026-07-29), still releasing

11

4.0

4.0.33 (2026-07-29)

8

3.0

3.0.25 (2025-05-24), the last 3.0 release; nothing for over a year

8

The official download page still described 6.0 as the next major version when this page was checked, while the change log and the 6.0 release notes list 6.0.0 as released. When in doubt, the change log is authoritative. Gradle, Jenkins pipelines and JMeter embed their own Groovy line, which is usually older than 6.0: check which one your tool ships before using a newer feature in its scripts (see Ecosystem and JMeter: Scripting with JSR223 and Groovy).

Timeline

timeline title Apache Groovy major and minor releases 2007 : 1.0 in January : 1.5 in December adds the Java 5 features 2009 : 1.6 adds AST transformations and Grape : 1.7 adds power asserts 2011 : 1.8 adds command chains and native JSON 2012 : 2.0 adds static typing and modules 2013 : 2.1 completes invokedynamic support : 2.2 adds closure coercion 2014 : 2.3 adds traits 2015 : 2.4 adds Android support and moves to Apache 2018 : 2.5 adds macros and the JDK 9 layout 2020 : 3.0 brings the Parrot parser 2022 : 4.0 moves to org.apache.groovy 2025 : 5.0 raises the JRE to 11 2026 : 5.1 hardens security : 6.0 adds async await and requires JDK 17

Legacy to modern

Older books, blog posts and Stack Overflow answers describe Groovy 1.x and 2.x. Most of what they teach still works, but several statements are now wrong and many idioms have a better replacement. The first table lists the specific places where a Groovy 1.0 reference (the era of the first generation of printed books) no longer matches Groovy 6.0; the second table lists further legacy-to-modern replacements. The guide pages always show the current form.

Where Groovy 1.0 material is out of date

Older statement or form Groovy 6.0 Page

The classic for(init; test; inc) loop is not available

Supported since 1.5; the enhanced forms (several variables in 3.0, an index variable in 5.0) exist too

Control Structures

There is no do/while loop

do/while has been available since 3.0 (the Parrot parser)

Control Structures

Java’s package-wide default visibility cannot be expressed

The @PackageScope AST transformation (1.6) gives package-private members

Classes, Fields & Properties

No lambdas, method references, !in/!instanceof, ===, ?=, safe indexing ?[], try-with-resources or Java-style array initialisers

All added by the Parrot parser in 3.0; the ==> operator in 5.0; val and module imports in 6.0

Differences with Java

Examples avoid annotations, enums, generics, static imports and varargs (they target a JDK that lacks them)

All supported since 1.5

Enums & Annotations

Metaprogramming means invokeMethod, MetaClass and categories

ExpandoMetaClass and methodMissing/propertyMissing (1.5), AST transformations (1.6), extension modules (2.0), traits (2.3, which replace @Mixin)

Runtime Metaprogramming & the MOP

Everything is dynamic

Optional static type checking and static compilation (@TypeChecked/@CompileStatic, 2.0)

Static Type Checking & @CompileStatic

JSR 241 makes Groovy the second standard language of the JVM, and Groovy is a Codehaus project

JSR 241 has been dormant since 2012; Groovy is a top-level Apache Software Foundation project

Groovy

groovy:groovy or org.codehaus.groovy coordinates, the groovy-all jar and the org.codehaus.groovy.* legacy packages

org.apache.groovy since 4.0; the legacy packages are gone; groovy-all is a POM that pulls in modules

Build Tools: Gradle, Maven & Ant

GroovyTestCase (JUnit 3) as the base class for tests

JUnit 5 or 6 with GroovyAssert, or Spock; GroovyTestCase is legacy

Testing with JUnit & Groovy Test

No built-in dependency management

Grape and @Grab (1.6); a Maven Resolver based engine in 6.0

Grape Dependency Management

Swing, Ant and SQL classes live in the core jar

Separate modules (groovy-swing, groovy-ant, groovy-sql, groovy-xml, groovy-json, and more) since 2.0

Databases with Groovy SQL

XML-RPC and GroovySOAP for remote calls

Those modules are no longer maintained; use the JDK HttpClient or the groovy-http-builder module (6.0, incubating)

HTTP Client with HttpBuilder

Groovlets run on javax.servlet

Jakarta Servlet is the default since 5.0; javax is still supported

Groovlets, Servlets & JMX

groovy -l listens on every interface

It binds the loopback address unless you give a host, and it has no authentication

Running Scripts & the groovy Command

The launchers append . to your classpath

Since 6.0 a classpath you supply is used exactly as written

Running Scripts & the groovy Command

Sandboxing relies on the Java Security Manager

Security Manager support was removed in 6.0; use SecureASTCustomizer, containers and operating system isolation

Compilation Customizers & Secure Scripting

groovysh is a simple line reader

Rewritten on JLine 3 in 5.0 with commands and completion; /img for inline images in 6.0

groovysh & groovyconsole

Call-site caching is how method dispatch works

invokedynamic is the default since 4.0; the classic runtime is the optional groovy-callsite module in 6.0

Performance, invokedynamic & Native Images

Concurrency means Thread.start and GPars as an add-on

Async/await, parallel collections, actors, agents and dataflow in groovy.concurrent (6.0, incubating)

Async/Await

Other legacy idioms and their replacements

Legacy idiom Modern replacement Page

groovy.util.XmlSlurper, groovy.util.XmlParser, groovy.util.AntBuilder, groovy.util.GroovyTestCase

groovy.xml.XmlSlurper, groovy.xml.XmlParser, groovy.ant.AntBuilder, groovy.test.GroovyTestCase (4.0 removed the duplicates)

XML Parsing, GPath & Security

@Mixin

Traits, which compose behaviour at compile time with proper inheritance rules

Traits

@Immutable classes written by hand, equals/hashCode/toString boilerplate

Records (4.0, incubating in 6.0), @Canonical/@TupleConstructor/@EqualsAndHashCode, @Immutable

Records & Sealed Types

java.util.Date and Calendar extension methods

The java.time API with Groovy’s extension methods; the Date helpers moved to the optional groovy-dateutil module in 2.5 and java.time became a default import in 5.0

Dates & Times

% handled by a mod method

remainder since 5.0 (a fallback to mod is kept)

Operator Overloading

Anonymous inner classes and Closure subclasses for callbacks

Lambdas and method references (3.0); closures still coerce to functional interfaces

Closures

if (file) to ask whether a file variable is set

file != null; since 5.0 File and Path are falsy when the path does not exist

Booleans & the Groovy Truth

switch with break in every branch

Switch expressions with -> arms (4.0); position decides statement or expression (6.0)

Control Structures

'org.mysql.Driver' as Class to register a JDBC driver

Drivers register through ServiceLoader; call Class.forName explicitly if you really need the side effect (6.0 no longer runs the static initialiser for the coercion)

Databases with Groovy SQL

Untyped def everywhere

var (3.0), val for final locals (6.0) and optional types that @CompileStatic checks

Variables, Typing, def, var & val

A parallel library such as GPars for futures

Native async/await, channels and structured concurrency (6.0, incubating)

Generators, Channels & Structured Concurrency

Hand-rolled SQL strings in loops

GINQ queries (4.0, stable in 6.0) and groovy.sql.Sql with bound parameters

GINQ: SQL-like Collection Queries

Hand-written JSON or XML parsing with default factories

JsonSlurper and the secure-by-default XML parsers (6.0)

JSON

Reflection tricks to call another language’s code

Joint compilation and the JavaShell (stable in 6.0)

Compiling with groovyc & Joint Compilation

Writing the same code in the old and the new style

The script below uses only forms that Groovy 1.0 did not have or that arrived with the Parrot parser and later. It runs unchanged on Groovy 6.0.0:

// Forms that Groovy 1.0 lacked or that only arrived with the Parrot parser (3.0) and later.
import java.util.function.Function

// classic for loop (1.5), do/while (3.0), Java-style array initializer (3.0)
int[] sizes = [3, 5, 8] as int[]
int[] more = new int[]{4, 6}
def total = 0
for (int i = 0; i < sizes.length; i++) { total += sizes[i] }
int n = 3
do { n-- } while (n > 0)

// lambdas and method references (3.0)
Function<String, Integer> len = s -> s.length()
def upper = 'tomato'.&toUpperCase

// negated operators, identity, Elvis assignment, safe indexing (3.0)
def seed = null
seed ?= 'basil'
List<String> none = null

// try-with-resources (3.0)
String first
try (def r = new StringReader('line1\nline2')) { first = new BufferedReader(r).readLine() }

// 5.0: loop with an index variable
def idx = []
for (int i, var item in ["a", "b", "c"]) { idx << "$i$item" }

// 6.0: val
val final1 = 42

assert total == 16 && more.length == 2 && n == 0
assert len('lettuce') == 7 && upper() == 'TOMATO'
assert 'x' !in ['a', 'b'] && !('x' instanceof Integer)
assert seed == 'basil' && none?[0] == null
assert first == 'line1'
assert idx == ['0a', '1b', '2c']
assert (false ==> true) && !(true ==> false)
assert final1 == 42
println 'modern syntax ok'

Output of the real run:

modern syntax ok

Breaking changes and how to migrate

Groovy keeps minor releases compatible; the breaking changes cluster in the major releases and are listed in the "Breaking changes" section of each release-notes page. The tables below cover the ones that affect typical projects.

Moving to Groovy 3.0

Groovy 3.0 introduced the Parrot parser (enabled by default; the old parser could still be selected by a system property) and, to prepare for the Java module system, duplicated many classes into JPMS-friendly packages. Nothing had to change yet, but this is the release in which you should start moving to the new package names, because 4.0 removes the old ones.

Moving to Groovy 4.0

Change What to do Where

Maven coordinates: the groupId changed from org.codehaus.groovy to org.apache.groovy

Update Gradle, Maven and Ant build files (the Gradle and Maven snippets are below)

Build Tools

Legacy packages removed: the duplicated classes in the old packages are gone

Replace groovy.util.XmlSlurper with groovy.xml.XmlSlurper, groovy.util.XmlParser with groovy.xml.XmlParser, groovy.util.AntBuilder with groovy.ant.AntBuilder, groovy.util.GroovyTestCase with groovy.test.GroovyTestCase, and so on for the classes listed in the 3.0 notes

XML Parsing, GPath & Security

Old parser removed: the Antlr 2 based parser no longer exists

Fix any source that only compiled with the old parser; if you cannot, stay on 3.0

Lexical Structure

invokedynamic is on by default: one set of jars, no -indy variants

Remove the indy classifier; set groovy.target.indy=false only if you must disable it

Performance, invokedynamic & Native Images

groovy-all contents: groovy-yaml is now included, groovy-testng is not

Add groovy-testng explicitly if you use it

Build Tools

Modules removed: groovy-jaxb (JAXB left the JDK) and groovy-bsf (BSF 2 reached end of life)

Use the Groovy 3 version of those modules or switch libraries

Integrating Groovy into Java Applications

intersect with a projection now draws elements from the receiver, as in other languages

Re-check calls of intersect that pass a closure or comparator

Working with Collections

A build file moves like this (build snippets, not run here; the coordinates were checked against Maven Central):

// Gradle: Groovy 3 and earlier
implementation 'org.codehaus.groovy:groovy:3.0.25'
// Gradle: Groovy 4 and later (groovy-all is a POM-only artifact; add the modules you need individually)
implementation 'org.apache.groovy:groovy:6.0.0'
implementation 'org.apache.groovy:groovy-json:6.0.0'
<!-- Maven: replace org.codehaus.groovy with org.apache.groovy -->
<dependency>
  <groupId>org.apache.groovy</groupId>
  <artifactId>groovy-all</artifactId>
  <version>6.0.0</version>
  <type>pom</type>
</dependency>

The package move is the most common 4.0 failure. This is the real compiler message for the old name on Groovy 6.0.0:

def xml = '<crops><crop name="basil"/><crop name="mint"/></crops>'
def root = new groovy.util.XmlSlurper().parseText(xml)
println root.crop.size()
legacy-package.groovy: 2: unable to resolve class groovy.util.XmlSlurper
 @ line 2, column 12.
   def root = new groovy.util.XmlSlurper().parseText(xml)
              ^

1 error

With the groovy.xml package the same script runs:

def xml = '<crops><crop name="basil"/><crop name="mint"/></crops>'
def root = new groovy.xml.XmlSlurper().parseText(xml)
println root.crop.size()
2

Moving to Groovy 5.0

The runtime minimum rises to JDK 11. The notable breaking changes:

Change What to do Where

Duplicate imports are errors: two imports of the same simple name, or an import and a class with that name, used to let the last one win

Remove or alias the duplicates

Program Structure, Packages, Imports & Scripts

java.time is a default import, so classes such as Year, Month and Duration in the default package now conflict

Rename them, move them into a package or alias the import

Dates & Times

File and Path truthiness: a path that does not exist is falsy

Test file != null for "is it set"; -Dgroovy.truth.file.exists.enabled=false restores the old behaviour while you port (from 5.0.7)

Booleans & the Groovy Truth

% calls remainder instead of mod

Rename an overload named mod; the fallback keeps old classes working

Operator Overloading

Scripts with only a static main no longer extend Script (JEP 512 compatibility)

Turn them into plain scripts or use an instance run method

Running Scripts & the groovy Command

Visibility and property access tightened for package-private members, anonymous classes and map-like classes; abstract/final on an enum is an error; some AST annotations have SOURCE retention

Compile and fix; framework authors should re-test AST transform annotations

Classes, Fields & Properties

Iterator extension methods such as findIndexValues and chop now return iterators; chop stops when the collection is used up

Add .toList() where you need a list

Working with Collections

The duplicate-import rule is easy to see. On Groovy 6.0.0 this script fails to compile:

import java.util.Date
import java.sql.Date
println Date
dup-import.groovy: 2: The name Date is already declared @ line 2, column 8.
   import java.sql.Date
          ^

1 error

The File truthiness change and its porting flag, run on Groovy 6.0.0 (the second run adds the system property):

def missing = new File('no-such-file.txt')
println missing.exists()
println(missing ? 'truthy' : 'falsy')
println(missing != null ? 'set' : 'unset')
false
falsy
set
false
truthy
set

An overload named remainder is now what % calls:

class Tray {
    int n
    Tray remainder(int k) { new Tray(n: n % k) }
    String toString() { "Tray($n)" }
}
println new Tray(n: 17) % 5
Tray(2)

Moving to Groovy 5.1

Groovy 5.1 keeps the 5.0 JDK requirements. Its breaking changes are small and security-motivated:

  • deleteDir() on File and Path treats symbolic links as leaves instead of following them into their targets. If you relied on deleting the contents of a linked directory, resolve the link and delete the target yourself.

  • Grape coordinates containing .. sequences are rejected.

  • The deprecated run(JApplet) method was removed from the Groovy Console so that it loads on JDK 26 and later.

Moving to Groovy 6.0

The runtime minimum rises to JDK 17 (Groovy 6.0 was tested on JDK 17 to 27). The headline breaking changes:

Change What to do Where

Security Manager support removed: Groovy no longer calls AccessController.doPrivileged or uses the related infrastructure (the Security Manager itself is deprecated for removal in the JDK)

Stop relying on policy files; restrict scripts with SecureASTCustomizer and isolate with containers and the operating system

Compilation Customizers & Secure Scripting

The launchers no longer add . to a classpath you supply

Add . yourself (-cp lib.jar:., or -cp lib.jar;. on Windows)

Running Scripts & the groovy Command

Switch: position decides: a switch whose value is used is an expression (an unmatched selector throws IllegalStateException, a missing default is an error under static checking); a switch whose value is unused is a statement

Add a default arm to expression switches; statement switches behave as in 4.x and 5.x

Control Structures

Generic types must be well formed: generic array creation, a generic class extending Throwable, wildcards in instance creation, primitive type arguments, new T() and similar constructs that older versions silently erased are compile errors

Use the workaround shown in the Generics section of the release notes (for example Array.newInstance or a raw array)

Inheritance, Interfaces & Generics

val is a keyword (contextual) for final locals

Rename a field or variable named val in the ambiguous positions, or set -Dgroovy.val.enabled=false while porting

Variables, Typing, def, var & val

key in map tests the key

Use map.containsValue(v) or v in map.values() to test values

Maps

A for-in loop variable captured by a closure or lambda has a fresh binding per iteration

Nothing, unless your code relied on the final value; -Dgroovy.forin.per.iteration.capture=false restores the old behaviour

Closures

The classic call-site runtime moved to the optional groovy-callsite module

Add the module only if you run with invokedynamic disabled

Performance, invokedynamic & Native Images

XML is secure by default; GroovyServlet hides error details; groovy-sql rejects GString queries with a quoted dynamic expression; JsonSlurper rejects numbers longer than 1000 characters

Use the documented opt-out properties only as porting aids

XML Parsing, GPath & Security

String-to-Class coercion no longer initialises the class

Call Class.forName(name) where the static initialiser matters

Databases with Groovy SQL

Method-level @TypeChecked/@CompileStatic now overrides a class-level SKIP

Remove the method annotation or give it SKIP mode

Static Type Checking & @CompileStatic

The classpath dot

A script that loads a class from the working directory while you also pass -cp for a library used to work because the launcher appended the current directory. With 6.0 the classpath you pass is used as is. The demonstration compiles a class Watering into the current directory and runs use.groovy, which calls it, three ways:

groovyc Watering.groovy
groovy use.groovy                        # no classpath given: falls back to the current directory
groovy -cp /nonexistent-dir use.groovy   # explicit classpath: the current directory is not added
groovy -cp "/nonexistent-dir:." use.groovy
water at dawn
SEVERE: Caught: {0}
groovy.lang.MissingPropertyException: No such property: Watering for class: use
...
water at dawn

Replacing the Security Manager

Setting a Security Manager is not possible on current JDKs, and Groovy 6 no longer cooperates with one. On JDK 26 the call fails immediately:

println System.getSecurityManager()
try { System.setSecurityManager(null) } catch (e) { println e.class.name }
null
java.lang.UnsupportedOperationException

For scripts that you do not fully trust, restrict what they may compile with a SecureASTCustomizer (and still run them in a container or an unprivileged operating-system account, because a compile-time allow-list is not a full sandbox):

import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.SecureASTCustomizer
import org.codehaus.groovy.control.MultipleCompilationErrorsException

def secure = new SecureASTCustomizer()
secure.with {
    // allow-list of receiver types for method calls: System.exit(...) is not on it
    allowedReceiversClasses = [Object, Integer, String, Math]
}
def config = new CompilerConfiguration()
config.addCompilationCustomizers(secure)
def shell = new GroovyShell(config)

println shell.evaluate('Math.max(2, 7)')
try {
    shell.evaluate('System.exit(1)')
} catch (MultipleCompilationErrorsException e) {
    println 'rejected at compile time: ' + e.errorCollector.getError(0).cause.message.readLines().first()
}
7
rejected at compile time: Method calls not allowed on [java.lang.System]

The switch position rule

Whether a switch is a statement or an expression no longer depends on the arrow: it depends on whether the value is used. A switch that ends a method is a statement whose matched value is returned by the implicit return (and is null when nothing matches, as in 4.x and 5.x); a switch on the right of an assignment is an expression and must be exhaustive:

def label(int i) {
    switch (i) {
        case 1 -> 'one'
        case 2 -> 'two'
    }
}
println label(1)
println label(9)
def viaExpr = { int i -> def r = switch (i) { case 1 -> 'one'; default -> 'many' }; r }
println viaExpr(7)
try {
    def s = 9
    def r = switch (s) { case 1 -> 'one' }
} catch (IllegalStateException e) { println "expression with no match: ${e.class.simpleName}" }
one
null
many
expression with no match: IllegalStateException

Generic syntax

Groovy 4 and 5 accepted generic constructs that Java rejects by quietly treating them as raw types. Groovy 6 applies the Java rules in every compilation unit, scripts included. The real error for generic array creation:

List<String>[] buckets = new List<String>[3]
generics.groovy: 1: generic array creation of List<java.lang.String>
 @ line 1, column 26.
   List<String>[] buckets = new List<String>[3]
                            ^

Use a List<List<String>> or a raw array with a cast instead. Source: Groovy documentation: Generic type syntax

Two behaviours that change silently

Two 6.0 changes compile and run without any message, so they deserve a test in your suite. The first is the in operator on a map, which now tests keys; the second is the for-in capture rule:

def stock = [basil: 0, mint: 3]
println('basil' in stock)      // key-based since 6.0
println(0 in stock)            // values are no longer tested
println(0 in stock.values())
true
false
true
def tasks = []
for (crop in ['basil', 'mint', 'sage']) { tasks << { crop } }
println tasks*.call()
[basil, mint, sage]

With -Dgroovy.forin.per.iteration.capture=false the same script prints [sage, sage, sage], which is what earlier releases did (every closure shared one binding).

Upgrade checklist

  1. Pick the target line. Use the maintained-lines table above; a project on JDK 8 can stay on 4.0, JDK 11 on 5.x, JDK 17 or later can take 6.0.

  2. Upgrade one major at a time if you are far behind (3.0, then 4.0, then 5.0, then 6.0): each release-notes page lists the breaking changes for that step only.

  3. Fix coordinates and imports first. Switch org.codehaus.groovy to org.apache.groovy, move legacy packages to their groovy.xml, groovy.ant, groovy.test and similar homes, and add any optional module that groovy-all no longer includes.

  4. Build and run the tests on the new line. Read compile errors against the tables above; run with the documented porting flags only to separate behavioural changes from syntax changes, and remove them afterwards.

  5. Check the toolchain. Gradle, Maven plugins, IDEs, Jenkins and JMeter embed their own Groovy line; see Build Tools: Gradle, Maven & Ant.

  6. Re-check tests and frameworks. Spock publishes one artifact per Groovy line (for example 2.5-groovy-6.0), see Testing with Spock & Geb.

  7. Adopt new features gradually. var, val, switch expressions, records and @CompileStatic are low risk; treat async/await and other incubating features as subject to change.

For the Java side of the same questions (language levels, the module system and build tooling) see Java: Build & Tooling and Java: Packages & Modules.