Groovy & Other Languages
|
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.0 (released 2026-09-22), run on JDK 26 and checked on 2026-10-14. It is a translation table for readers who already know another language: for each everyday idiom it shows the Groovy form next to the form in Java, Kotlin, Scala, Python and Ruby, runs the Groovy side, and then discusses what dynamic and static typing cost and buy you. It deliberately stays at the level of idioms. The exact differences from Java, row by row, are on Differences with Java.
The other languages are shown only as far as needed to make the comparison readable; their own sections go further. The Kotlin and Python references on this site are Kotlin and Python. The Scala (#299) and Ruby (#276) references are tracked as separate issues and are mentioned here by number only, because their pages do not exist yet.
Where Groovy sits
Groovy is an optionally typed language for the JVM. Its source compiles to ordinary JVM classes, so it calls Java
libraries and is called from Java with no wrapper layer. It keeps Java’s braces, classes and packages, and adds
closures, literals for lists, maps and ranges, operators such as ?. and ?:, string interpolation, properties
and a large library of methods added to existing JDK types (the GDK). Whether a given line is checked at compile time
or at run time is your choice, line by line or class by class (see Dynamic versus static typing).
| Compared with | Short answer |
|---|---|
Java |
Almost every Java source file is already valid Groovy; Groovy removes ceremony and adds dynamic features. See the row-by-row list of differences. |
Kotlin |
Both are concise JVM languages with Java-like syntax and lambdas. Kotlin is statically typed with null-safety in the type system; Groovy is dynamic by default with run-time null-safe operators, and has stronger metaprogramming. |
Scala |
Scala is statically typed with a far richer type system and functional emphasis. Groovy trades type-level guarantees for less ceremony and for scripting. |
Python |
Groovy is the closest JVM equivalent of Python’s scripting style: dynamic, concise, with literals and a batteries-included library, but with braces and the JVM’s performance and ecosystem. |
Ruby |
Groovy’s closures, open classes, builders and optional parentheses are close to Ruby’s blocks, open classes and DSL style. Ruby is dynamic only; Groovy can also be compiled statically. |
Idiom mapping
The tables list the idiom as it is written in each language. Code in the Groovy column is real Groovy 6.0; the other columns are the usual spelling in a current release of that language and are given for orientation only.
Collection literals
| Language | List and map literals |
|---|---|
Java |
|
Kotlin |
|
Scala |
|
Python |
|
Ruby |
|
Groovy |
|
// Collection literals, closures, spread and the GDK
def temperatures = [18.4, 18.9, 19.6]
def zones = [tomatoes: 'bench-1', basil: 'basket-1']
def hot = temperatures.findAll { it > 18.5 }
def names = zones.collect { plant, zone -> "$plant@$zone" }
assert temperatures.class == ArrayList
assert zones.getClass() == LinkedHashMap
assert hot == [18.9, 19.6]
assert names == ['tomatoes@bench-1', 'basil@basket-1']
assert zones*.key == ['tomatoes', 'basil']
assert temperatures.sum() / temperatures.size() == 18.9666666667
println temperatures.sum() / temperatures.size()
println temperatures.sum().getClass().name
Output of the two println calls, produced by running the script:
18.9666666667
java.math.BigDecimal
Two details are worth noting. Decimal literals such as 18.4 are BigDecimal, not double, so the average is exact to
ten decimals and printed with a limited scale; this differs from Java, Kotlin, Python and Ruby, where 18.4 is a binary
floating-point value. The spread operator *. calls the property or method on every element and collects the results,
which is what a Python list comprehension or a Ruby map(&:key) does.
Closures and lambdas
| Language | Anonymous function that doubles its argument |
|---|---|
Java |
|
Kotlin |
|
Scala |
|
Python |
|
Ruby |
|
Groovy |
|
A Groovy closure is an object of class groovy.lang.Closure. Unlike a Java lambda it carries an owner and a
delegate that decide where unqualified names are resolved, and that is what makes builders and DSLs possible (see
Closure Delegation: Owner, Delegate &
This). A closure is converted automatically to a functional interface when passed to a Java API that expects one.
For Java lambdas see Lambdas & Method References;
the Groovy closures are covered on Closures.
Null safety and defaults
| Language | Reading a possibly-null chain, with a default |
|---|---|
Java |
|
Kotlin |
|
Scala |
|
Python |
|
Ruby |
|
Groovy |
|
class Zone { String name; Zone parent }
def bench = new Zone(name: 'bench-1')
assert bench.parent?.name == null // safe navigation
assert (bench.parent?.name ?: 'none') == 'none' // Elvis
assert bench.parent?.name?.size() == null
String maybe = null
assert (maybe ?: 'default') == 'default'
// Groovy truth: 0, '' and empty collections are false
assert !0 && !'' && ![] && ![:] && !null
assert 'x' && [1] && 1
The script ran without output and with every assertion true.
The caveat is the Groovy truth: ?: replaces every value that is false, not only null. 0, the empty string, an
empty collection and an empty map are all false, exactly as in Python and unlike Kotlin or Ruby, where only null and
false are. Use ?. plus an explicit == null test when 0 or '' is a legitimate value. Nothing in Groovy’s types
stops a null from reaching a method call; the failure shows up as a NullPointerException or a
MissingMethodException at run time, unless a static checker is active (see Dynamic versus static typing).
String interpolation
| Language | Embedding values in text |
|---|---|
Java |
|
Kotlin |
|
Scala |
|
Python |
|
Ruby |
|
Groovy |
|
import groovy.transform.ToString
import groovy.transform.EqualsAndHashCode
@ToString @EqualsAndHashCode
class Plant {
String name
int everyHours = 12
}
def tomatoes = new Plant(name: 'tomatoes', everyHours: 2)
tomatoes.everyHours += 1 // calls the generated getter and setter
println tomatoes
assert tomatoes == new Plant(name: 'tomatoes', everyHours: 3)
def name = 'basil'
def hours = 12
println "Water $name every ${hours} hours; total ${hours * 2}"
println """\
zone: bench-1
plant: $name"""
Plant(tomatoes, 3)
Water basil every 12 hours; total 24
zone: bench-1
plant: basil
A double-quoted string containing $ is a GString, a lazily evaluated object that is not a java.lang.String. It
converts to String when you assign it to a String variable or pass it to a method that takes one, but it has a
different hashCode, so a GString used as a Map key or compared with equals to a String surprises newcomers
(covered on Strings & GStrings).
Properties and constructors
| Language | A mutable property with a default value |
|---|---|
Java |
a private field with |
Kotlin |
|
Scala |
|
Python |
|
Ruby |
|
Groovy |
|
The example above shows all of it: tomatoes.everyHours += 1 reads and writes the property through its generated
accessors, and @ToString/@EqualsAndHashCode supply toString() and value equality. Declaring a property with a
modifier or a field with an explicit visibility changes what is generated. Explicit getter and setter methods you write
yourself win over the generated ones. The full rules are on
Classes, Fields & Properties.
Extension methods and open classes
| Language | Adding behaviour to a type you do not own |
|---|---|
Java |
not possible; use a static utility method ( |
Kotlin |
extension functions, |
Scala |
implicit classes (Scala 2) or |
Python |
plain functions or subclassing; assigning to a built-in type is not allowed, and monkey-patching your own classes is discouraged |
Ruby |
open classes ( |
Groovy |
categories with |
class PlantCategory {
static String label(String self) { self.capitalize() + ' (plant)' }
}
use(PlantCategory) {
assert 'basil'.label() == 'Basil (plant)'
}
// outside the use block the method does not exist
try {
'basil'.label()
} catch (MissingMethodException e) {
println e.message.readLines().first()
}
// ExpandoMetaClass: Ruby-style open classes
String.metaClass.shout = { -> delegate.toUpperCase() + '!' }
assert 'water'.shout() == 'WATER!'
No signature of method: label for class: java.lang.String is applicable for argument types: () values: []
A category is scoped to the code block and the current thread; an ExpandoMetaClass change is global for the JVM until
it is removed; an extension module is global and also visible to the static compiler. See
Metaclasses, ExpandoMetaClass &
Categories and Extension Modules. Kotlin’s extension
functions are compile-time sugar for static functions, so unlike Groovy’s they cannot be added at run time or override a
member.
Domain-specific languages
| Language | Mechanism |
|---|---|
Java |
fluent builders and method chaining; no operator or syntax customisation |
Kotlin |
lambdas with receivers, |
Scala |
infix calls, implicit conversions, by-name parameters, macros |
Python |
operator overloading, decorators, context managers |
Ruby |
blocks run with |
Groovy |
optional parentheses and dots (command chains), closures with a |
// Parentheses and dots are optional in a chain of commands:
// water 'tomatoes' every 2 hours == water('tomatoes').every(2).hours
def water(String plant) {
[every: { int n -> [hours: "$plant every $n hours"] }]
}
def line = water 'tomatoes' every 2 hours
assert line == 'tomatoes every 2 hours'
println line
tomatoes every 2 hours
This is the shape of the greenhouse watering DSL built on Domain Specific Languages. The same mechanism is why the Gradle Groovy DSL, Spock specifications and Jenkins pipeline scripts read like configuration. The Ruby analogue is covered by the Ruby reference tracked as #276.
Dynamic versus static typing
Groovy is dynamic by default: a method call is resolved when the line executes, by looking at the run-time types of the
receiver and the arguments (multi-methods). That is the same model as Python and Ruby and it gives the same benefits
and the same costs. Adding @CompileStatic or @TypeChecked to a class or a method switches that code to a
Java-like model in which the compiler checks the call.
import groovy.transform.CompileStatic
def dynamic(x) { x.size() } // resolved at run time
assert dynamic('basil') == 5
assert dynamic([1, 2]) == 2
@CompileStatic
int checked(String s) { s.size() } // resolved at compile time
assert checked('basil') == 5
try {
dynamic(42)
} catch (MissingMethodException e) {
println e.message.readLines().first()
}
No signature of method: size for class: java.lang.Integer is applicable for argument types: () values: []
The same mistake in a @CompileStatic method is rejected before the script starts:
statictrap.groovy: 3: [Static type checking] - Cannot find matching method java.lang.String#sizee(). Please check if the declared type is correct and if the method exists.
@ line 3, column 21.
| Aspect | Dynamic Groovy | @TypeChecked / @CompileStatic |
|---|---|---|
Errors such as a misspelt method |
found when the line runs ( |
found at compile time |
Duck typing and |
natural |
still allowed, but the call must type-check |
Runtime metaprogramming (categories, |
works |
|
Performance |
dispatch through the metaclass and |
direct bytecode like Java, with |
Refactoring and IDE support |
weaker |
close to Java |
The usual practice, reflected in the official style guide, is to leave scripts, tests and DSL code dynamic, and to put
@CompileStatic on library and service code where a type error should stop the build. The mechanisms and their limits
are on Static Type Checking & @CompileStatic
and Type Checking Extensions.
Where Groovy fits on the JVM
| Use | Why Groovy |
|---|---|
Scripting and automation |
|
Build files |
the Gradle Groovy DSL and Ant-style builders; Gradle also has a Kotlin DSL |
Tests |
Spock specifications and JUnit tests with power assertions read compactly |
Embedded scripting |
a Java application can evaluate Groovy at run time with |
Data wrangling |
literals plus |
Large statically typed code bases |
possible with |
The scenario for the rest of the section, the greenhouse toolkit, plays to these strengths: reading a sensor log, building a schedule from a small DSL and scripting a report. The same code in Java would be several times longer; in Kotlin it would be about as long but would not run as a plain script file.
Where the book is outdated
The first edition of the reference book (Groovy 1.0, 2007) predates most features compared here. It has no lambdas or
method references, no ?[] index-safe operator, no traits, no @CompileStatic, and its idiom for extending types is
ExpandoMetaClass or a category. Groovy 1.0 also had no annotations, enums or generics in the examples, since it ran on
JDK 1.4. Today the book’s comparisons to Java should be read against the Groovy 6.0 rows above. The one-line summary of
each change is collected on
What’s New & Migrating from Older Versions.