NexusCS

Groovy

Java ecosystem
Groovy is a powerful, optionally typed and dynamic language for the JVM with static compilation capabilities. This guide covers Groovy syntax, operators, closures, and Gradle DSL.
jvm
scripting
gradle

Getting started

Introduction

Groovy is a Java-syntax-compatible object-oriented programming language for the JVM. It compiles to JVM bytecode and seamlessly integrates with Java code.

Key Features

  • Optional typing - Dynamic or static
  • Closures - First-class functions
  • Operator overloading - Custom operators
  • Native syntax - Lists, maps, ranges
  • AST transformations - Metaprogramming
  • Gradle - Build automation DSL

Installation

# SDKMAN (recommended)
sdk install groovy

# Homebrew (macOS)
brew install groovy

# Manual download
wget https://groovy.apache.org/download.html

Verify installation:

groovy -version
# Groovy Version: 4.0.x JVM: 17.0.x

Hello World

// Script (no class needed)
println "Hello, World!"

// With class
class Hello {
    static void main(String[] args) {
        println "Hello, World!"
    }
}

Run with:

groovy hello.groovy

Quick Example

// List operations
def numbers = [1, 2, 3, 4, 5]
def doubled = numbers.collect { it * 2 }
println doubled  // [2, 4, 6, 8, 10]

// Map operations
def person = [name: 'John', age: 30]
println person.name  // John

// Closure
def greet = { name -> "Hello, $name!" }
println greet('Alice')  // Hello, Alice!

Syntax Basics

Variables and Types

// Dynamic typing (def)
def name = "John"
def age = 30
def price = 19.99

// Static typing
String city = "New York"
int count = 10
List<String> items = ['a', 'b']

// Multiple assignment
def (a, b, c) = [1, 2, 3]

// Type coercion
def text = "123"
int number = text as int

Strings

// Single quotes (literal)
def literal = 'Hello'

// Double quotes (GString - interpolation)
def name = "World"
def greeting = "Hello, $name!"
def complex = "Result: ${1 + 1}"

// Triple quotes (multiline)
def multiline = '''
    Line 1
    Line 2
    Line 3
'''

// Slashy strings (regex-friendly)
def pattern = /\d+\.\d+/
def path = /C:\Users\path/  // No escaping

// Dollar slashy (multiline regex)
def sql = $/
    SELECT * FROM users
    WHERE name = 'O''Reilly'
/$

String Methods

def str = "Groovy"

str.size()           // 6
str.length()         // 6
str.toUpperCase()    // GROOVY
str.toLowerCase()    // groovy
str.reverse()        // yvoorG
str.capitalize()     // Groovy
str.center(10)       // "  Groovy  "
str.padLeft(8, '0')  // "00Groovy"

// Substring
str[0]               // G
str[0..2]            // Gro
str[-1]              // y (last char)
str[-3..-1]          // ovy

// Pattern matching
str ==~ /G.*y/       // true (matches)
str =~ /o+/          // Matcher object

Numbers

// Integer types
def i = 10
def big = 10G         // BigInteger
def longNum = 100L    // Long

// Decimal types
def decimal = 3.14
def bigDec = 3.14G    // BigDecimal
def floatNum = 3.14f  // Float

// Operators
10.power(2)           // 100
10 ** 2               // 100 (power)
10.div(3)             // 3 (integer div)
10 / 3                // 3.333...
10.intdiv(3)          // 3
10 % 3                // 1 (modulo)

// Ranges
1..10                 // Inclusive range
1..<10                // Exclusive end
10..1                 // Reverse range
'a'..'z'              // Character range

Collections

Lists

// Creation
def list = [1, 2, 3, 4, 5]
def empty = []
def mixed = [1, 'two', 3.0, [4]]

// Access
list[0]               // 1
list[-1]              // 5 (last)
list[1..3]            // [2, 3, 4]

// Modification
list << 6             // Append: [1,2,3,4,5,6]
list + [7, 8]         // Concatenate
list - [2, 4]         // Remove: [1,3,5]
list[0] = 10          // Set element
list.add(7)           // Add element
list.remove(2)        // Remove at index

// Operations
list.size()           // 5
list.isEmpty()        // false
list.contains(3)      // true
list.reverse()        // [5,4,3,2,1]
list.sort()           // [1,2,3,4,5]
list.unique()         // Remove duplicates
list.flatten()        // Flatten nested lists

List Methods

def numbers = [1, 2, 3, 4, 5]

// Iteration
numbers.each { println it }
numbers.eachWithIndex { val, idx -> 
    println "$idx: $val" 
}

// Transformation
numbers.collect { it * 2 }        // [2,4,6,8,10]
numbers.findAll { it > 2 }        // [3,4,5]
numbers.find { it > 2 }           // 3 (first)
numbers.grep { it > 2 }           // [3,4,5]

// Reduction
numbers.sum()                     // 15
numbers.max()                     // 5
numbers.min()                     // 1
numbers.join(', ')                // "1, 2, 3, 4, 5"
numbers.inject(0) { sum, n -> 
    sum + n 
}  // 15

// Testing
numbers.any { it > 3 }            // true
numbers.every { it > 0 }          // true

Maps

// Creation
def map = [name: 'John', age: 30]
def empty = [:]
def nested = [
    person: [name: 'Jane', age: 25]
]

// Access
map.name              // John
map['name']           // John
map.get('name')       // John
map.get('missing', 'default')  // default

// Modification
map.city = 'NYC'      // Add/update
map['country'] = 'USA'
map.put('zip', '10001')
map.remove('age')

// Operations
map.size()            // Number of entries
map.isEmpty()         // false
map.containsKey('name')       // true
map.containsValue('John')     // true
map.keySet()          // [name, age]
map.values()          // [John, 30]

Map Methods

def map = [a: 1, b: 2, c: 3]

// Iteration
map.each { key, value ->
    println "$key: $value"
}

map.each { entry ->
    println "${entry.key}: ${entry.value}"
}

// Transformation
map.collect { k, v -> v * 2 }     // [2,4,6]
map.findAll { k, v -> v > 1 }     // [b:2, c:3]
map.find { k, v -> v == 2 }       // b=2

// Combining
map + [d: 4]          // Merge
map - [b: 2]          // Remove by entry
map.subMap(['a', 'c'])  // [a:1, c:3]

Ranges

// Number ranges
def range = 1..10     // Inclusive
def exclusive = 1..<10  // Exclusive end

// Operations
range.size()          // 10
range.contains(5)     // true
range.from            // 1
range.to              // 10

// Iteration
range.each { println it }
range.step(2) { println it }  // 1,3,5,7,9

// Character ranges
('a'..'z').each { print it }

// Reverse
(10..1).each { println it }

// In switch
def rating = 5
switch(rating) {
    case 1..3: println 'Low'; break
    case 4..6: println 'Medium'; break
    case 7..10: println 'High'; break
}

Operators

Safe Navigation (?.)

def person = null

// Traditional null check
if (person != null) {
    println person.name
}

// Safe navigation
println person?.name  // null (no NPE)

// Chaining
def city = person?.address?.city

// With methods
person?.getName()?.toUpperCase()

// In assignments
def name = person?.name ?: 'Unknown'

Elvis Operator (?:)

// Default values
def name = null
def displayName = name ?: 'Anonymous'

// Chaining
def result = first ?: second ?: third ?: 'default'

// With safe navigation
def city = person?.city ?: 'Unknown'

// Method calls
def length = text?.length() ?: 0

// Assignment
name = name ?: 'Default'
// Same as: name ?: (name = 'Default')

Spaceship Operator (<=>)

// Comparison (-1, 0, 1)
1 <=> 2               // -1
2 <=> 2               // 0
3 <=> 2               // 1

// Sorting
def list = [3, 1, 2]
list.sort { a, b -> a <=> b }  // [1,2,3]

// Custom comparison
class Person {
    String name
    int age
}

people.sort { a, b ->
    a.age <=> b.age ?: a.name <=> b.name
}

Spread Operator (*.)

// Spread-dot (invoke on all)
def names = ['John', 'Jane', 'Bob']
names*.toUpperCase()  // [JOHN, JANE, BOB]

// Method calls
def lengths = names*.length()  // [4,4,3]

// Null-safe spread
def mixed = ['John', null, 'Jane']
mixed*.toUpperCase()  // [JOHN, null, JANE]

// Spread in lists
def list = [1, 2, 3]
def combined = [0, *list, 4]  // [0,1,2,3,4]

// Spread in maps
def base = [a: 1, b: 2]
def extended = [*:base, c: 3]  // [a:1,b:2,c:3]

// Spread in method calls
def sum(a, b, c) { a + b + c }
def nums = [1, 2, 3]
sum(*nums)            // 6

Field Access (.@)

class Person {
    private String name = 'John'
    
    String getName() { "Mr. $name" }
}

def person = new Person()

person.name           // Mr. John (via getter)
person.@name          // John (direct field)

Method Pointer (.&)

def str = "Hello"

// Method reference
def method = str.&toUpperCase
method()              // HELLO

// As closure
def numbers = [1, 2, 3]
numbers.each(System.out.&println)

// Constructor reference
def factory = Date.&new
def now = factory()

Other Operators

// Identity (===)
def a = [1, 2]
def b = [1, 2]
def c = a

a == b                // true (equals)
a === b               // false (not same object)
a === c               // true (same object)

// Regex (=~ and ==~)
def text = "Groovy 123"
text =~ /\d+/         // Matcher (partial match)
text ==~ /Groovy \d+/ // Boolean (full match)

// Membership (in)
3 in [1, 2, 3]        // true
'key' in [key: 'val'] // true

// Coercion (as)
"123" as Integer      // 123
[1, 2] as Set         // [1,2] (Set)
{ it * 2 } as Runnable

Closures

Closure Basics

// Simple closure
def greet = { "Hello!" }
println greet()       // Hello!

// With parameter
def greet2 = { name -> "Hello, $name!" }
println greet2('John')  // Hello, John!

// Multiple parameters
def add = { a, b -> a + b }
println add(2, 3)     // 5

// Implicit parameter (it)
def double = { it * 2 }
println double(5)     // 10

// No parameters
def random = { -> Math.random() }

// Optional typing
def typed = { int x, int y -> x + y }

Closure Features

// Default parameters
def greet = { name = 'World' ->
    "Hello, $name!"
}
greet()               // Hello, World!
greet('Alice')        // Hello, Alice!

// Variable arguments
def sum = { int... args ->
    args.sum()
}
sum(1, 2, 3)          // 6

// Return value (implicit)
def max = { a, b ->
    a > b ? a : b     // Last expression
}

// Explicit return
def max2 = { a, b ->
    return a > b ? a : b
}

Closure Delegation

class Person {
    String name
}

def closure = {
    println name      // Accesses delegate.name
}

def person = new Person(name: 'John')
closure.delegate = person
closure()             // John

// Delegation strategy
closure.resolveStrategy = 
    Closure.DELEGATE_FIRST
// DELEGATE_FIRST, OWNER_FIRST,
// DELEGATE_ONLY, OWNER_ONLY

Closures in Collections

def numbers = [1, 2, 3, 4, 5]

// each
numbers.each { println it }

// collect (map)
def doubled = numbers.collect { it * 2 }

// findAll (filter)
def evens = numbers.findAll { it % 2 == 0 }

// inject (reduce)
def sum = numbers.inject(0) { acc, n ->
    acc + n
}

// groupBy
def grouped = numbers.groupBy { it % 2 }
// [1:[1,3,5], 0:[2,4]]

// collectEntries
def map = numbers.collectEntries {
    [(it): it * it]
}  // [1:1, 2:4, 3:9, 4:16, 5:25]

Closure Composition

def add2 = { it + 2 }
def multiply3 = { it * 3 }

// Right shift (then)
def combined = add2 >> multiply3
combined(5)           // 21 = (5+2)*3

// Left shift (compose)
def combined2 = add2 << multiply3
combined2(5)          // 17 = (5*3)+2

// Memoization
def fib
fib = { n ->
    n < 2 ? n : fib(n-1) + fib(n-2)
}.memoize()

// Currying
def multiply = { a, b -> a * b }
def double = multiply.curry(2)
double(5)             // 10

Object-Oriented

Classes

// Simple class
class Person {
    String name
    int age
}

// With constructor
class Person {
    String name
    int age
    
    Person(String name, int age) {
        this.name = name
        this.age = age
    }
}

// Usage
def person = new Person('John', 30)
def person2 = new Person(
    name: 'Jane', 
    age: 25
)  // Named parameters

Properties

class Person {
    // Public property (auto getter/setter)
    String name
    
    // Private field
    private int age
    
    // Computed property
    String getFullName() {
        "$firstName $lastName"
    }
    
    // Custom setter
    void setAge(int age) {
        if (age > 0) this.age = age
    }
}

def p = new Person()
p.name = 'John'       // Calls setName()
println p.name        // Calls getName()
p.@name = 'Jane'      // Direct field access

Methods

class Calculator {
    // Instance method
    def add(a, b) { a + b }
    
    // Static method
    static def multiply(a, b) { a * b }
    
    // Default parameters
    def greet(name = 'World') {
        "Hello, $name!"
    }
    
    // Variable arguments
    def sum(int... numbers) {
        numbers.sum()
    }
    
    // Named parameters (via Map)
    def create(Map params) {
        println params.name
        println params.age
    }
}

def calc = new Calculator()
calc.add(2, 3)
Calculator.multiply(4, 5)
calc.create(name: 'John', age: 30)

Inheritance

class Animal {
    String name
    
    def speak() {
        "Some sound"
    }
}

class Dog extends Animal {
    @Override
    def speak() {
        "Woof!"
    }
    
    def fetch() {
        "Fetching..."
    }
}

def dog = new Dog(name: 'Rex')
dog.speak()           // Woof!

Interfaces and Traits

// Interface
interface Flyable {
    void fly()
}

// Trait (with implementation)
trait Swimmable {
    void swim() {
        println "Swimming..."
    }
}

class Duck implements Flyable, Swimmable {
    void fly() {
        println "Flying..."
    }
}

def duck = new Duck()
duck.fly()
duck.swim()

// Trait with state
trait HasId {
    long id
    
    void generateId() {
        id = System.currentTimeMillis()
    }
}

AST Transformations

import groovy.transform.*

// Canonical (constructor, toString, etc.)
@Canonical
class Person {
    String name
    int age
}

// Immutable
@Immutable
class Point {
    int x, y
}

// Singleton
@Singleton
class Database {
    def connect() { }
}

// ToString
@ToString(includeNames=true)
class Product {
    String name
    BigDecimal price
}

// TupleConstructor
@TupleConstructor
class User {
    String username
    String email
}

// Builder
@Builder
class Config {
    String host
    int port
    boolean ssl
}

def config = Config.builder()
    .host('localhost')
    .port(8080)
    .ssl(true)
    .build()

Metaprogramming

ExpandoMetaClass

// Add method to existing class
String.metaClass.shout = {
    delegate.toUpperCase() + "!"
}

"hello".shout()       // HELLO!

// Add static method
Integer.metaClass.static.isEven = { int n ->
    n % 2 == 0
}

Integer.isEven(4)     // true

// Add property
String.metaClass.getReversed = {
    delegate.reverse()
}

"groovy".reversed     // yvoorg

Missing Method/Property

class Dynamic {
    def methodMissing(String name, args) {
        println "Called: $name with $args"
    }
    
    def propertyMissing(String name) {
        println "Get: $name"
        return "Value of $name"
    }
    
    def propertyMissing(String name, value) {
        println "Set: $name = $value"
    }
}

def obj = new Dynamic()
obj.anything()        // Called: anything...
obj.someProp          // Get: someProp
obj.someProp = 123    // Set: someProp = 123

Categories

class StringExtensions {
    static String twice(String self) {
        self + self
    }
}

use(StringExtensions) {
    println "Groovy".twice()  // GroovyGroovy
}

// Multiple categories
use([Category1, Category2]) {
    // Both categories active here
}

@Delegate

class Worker {
    def work() { "Working..." }
}

class Manager {
    @Delegate Worker worker = new Worker()
    
    def manage() { "Managing..." }
}

def mgr = new Manager()
mgr.work()            // Delegated to Worker
mgr.manage()          // Manager's own method

File I/O

Reading Files

// Read entire file
def text = new File('file.txt').text

// Read lines
def lines = new File('file.txt').readLines()

// Iterate lines
new File('file.txt').eachLine { line ->
    println line
}

// With encoding
new File('file.txt')
    .getText('UTF-8')

// Read bytes
def bytes = new File('file.bin').bytes

// Using withReader
new File('file.txt').withReader { reader ->
    // Auto-closes
    reader.eachLine { println it }
}

Writing Files

// Write text
new File('out.txt').text = "Hello"

// Write lines
new File('out.txt').withWriter { writer ->
    writer.writeLine("Line 1")
    writer.writeLine("Line 2")
}

// Append
new File('out.txt').append("More text")

// Write bytes
new File('out.bin').bytes = [1, 2, 3]

// Using << operator
new File('out.txt') << "Appended text"

File Operations

def file = new File('example.txt')

// Properties
file.exists()
file.isFile()
file.isDirectory()
file.canRead()
file.canWrite()
file.size()
file.absolutePath
file.parent
file.name

// Operations
file.mkdir()          // Create directory
file.mkdirs()         // Create with parents
file.delete()         // Delete
file.renameTo(new File('new.txt'))

// Directory listing
new File('.').eachFile { file ->
    println file.name
}

new File('.').eachFileRecurse { file ->
    println file.absolutePath
}

Path Manipulation

// Join paths
def path = ['path', 'to', 'file.txt']
    .join(File.separator)

// Using / operator
def file = new File('dir') / 'subdir' / 'file.txt'

// Relative paths
def file1 = new File('.')
def file2 = new File('..', 'file.txt')

// Canonical path
file.canonicalPath

Gradle DSL

Build Script Basics

// build.gradle
plugins {
    id 'java'
    id 'application'
}

group = 'com.example'
version = '1.0.0'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.slf4j:slf4j-api:2.0.0'
    testImplementation 'junit:junit:4.13.2'
}

application {
    mainClass = 'com.example.Main'
}

Task Definition

// Simple task
task hello {
    doLast {
        println 'Hello, Gradle!'
    }
}

// Typed task
task copy(type: Copy) {
    from 'src'
    into 'dest'
}

// With configuration
task build {
    group = 'build'
    description = 'Builds the project'
    
    doFirst {
        println 'Starting build...'
    }
    
    doLast {
        println 'Build complete!'
    }
}

// Task dependencies
task compileJava
task test(dependsOn: compileJava)

// Multiple dependencies
task deploy(dependsOn: ['build', 'test'])

Dependencies

dependencies {
    // Implementation (compile-time)
    implementation 'group:artifact:version'
    
    // API (exposed to consumers)
    api 'group:artifact:version'
    
    // Runtime only
    runtimeOnly 'mysql:mysql-connector:8.0.30'
    
    // Test dependencies
    testImplementation 'junit:junit:4.13.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
    
    // Platform/BOM
    implementation platform('org.springframework.boot:spring-boot-dependencies:3.0.0')
    
    // Local file
    implementation files('libs/local.jar')
    
    // Project dependency
    implementation project(':subproject')
}

Configurations

// Custom configuration
configurations {
    customConfig
}

dependencies {
    customConfig 'group:artifact:version'
}

// Exclude transitive dependency
dependencies {
    implementation('group:artifact:version') {
        exclude group: 'unwanted', module: 'module'
    }
}

// Force version
configurations.all {
    resolutionStrategy {
        force 'group:artifact:1.0'
    }
}

Multi-Project Build

// settings.gradle
rootProject.name = 'my-project'
include 'app', 'lib', 'utils'

// Root build.gradle
subprojects {
    apply plugin: 'java'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        testImplementation 'junit:junit:4.13.2'
    }
}

// app/build.gradle
dependencies {
    implementation project(':lib')
    implementation project(':utils')
}

Custom Tasks

// Task class
class GreetingTask extends DefaultTask {
    @Input
    String greeting = 'Hello'
    
    @TaskAction
    void greet() {
        println "$greeting, Gradle!"
    }
}

task greet(type: GreetingTask) {
    greeting = 'Hi'
}

// Incremental task
task processFiles {
    inputs.dir('src')
    outputs.dir('build/processed')
    
    doLast {
        // Processing logic
    }
}

Advanced Topics

Regular Expressions

// Pattern matching
def text = "Groovy 4.0"

// Find operator (=~)
def matcher = text =~ /\d+\.\d+/
if (matcher) {
    println matcher[0]    // 4.0
}

// Exact match (==~)
text ==~ /Groovy \d+\.\d+/  // true

// Pattern object
def pattern = ~/\d+/
def matcher2 = pattern.matcher("123")
matcher2.matches()        // true

// Replace
text.replaceAll(/\d+/, 'X')  // Groovy X.X

// Split
"a,b,c".split(/,/)       // [a, b, c]

// Groups
def m = "John Doe" =~ /(\w+) (\w+)/
m[0][1]                  // John (group 1)
m[0][2]                  // Doe (group 2)

Exception Handling

// Try-catch
try {
    def result = 10 / 0
} catch (ArithmeticException e) {
    println "Cannot divide by zero"
} catch (Exception e) {
    println "Error: ${e.message}"
} finally {
    println "Cleanup"
}

// Try with resources
new File('file.txt').withReader { reader ->
    // Auto-closes
}

// Safe navigation
def length = text?.length()  // No NPE

// Elvis with exception
def value = mayFail() ?: 'default'

Type Checking

import groovy.transform.TypeChecked

@TypeChecked
class Typed {
    int add(int a, int b) {
        return a + b
        // Type errors caught at compile-time
    }
}

// Compile-time static
import groovy.transform.CompileStatic

@CompileStatic
class Fast {
    int multiply(int a, int b) {
        a * b  // No dynamic dispatch
    }
}

JSON Processing

import groovy.json.*

// Parse JSON
def json = '{"name":"John","age":30}'
def obj = new JsonSlurper().parseText(json)
println obj.name          // John

// Generate JSON
def person = [name: 'Jane', age: 25]
def jsonStr = JsonOutput.toJson(person)

// Pretty print
println JsonOutput.prettyPrint(jsonStr)

// With builder
def builder = new JsonBuilder()
builder.person {
    name 'John'
    age 30
    address {
        city 'NYC'
    }
}
println builder.toString()

XML Processing

// Parse XML
def xml = '''
<person>
    <name>John</name>
    <age>30</age>
</person>
'''

def person = new XmlSlurper().parseText(xml)
println person.name       // John
println person.age        // 30

// Generate XML
def builder = new groovy.xml.MarkupBuilder()
builder.person {
    name 'Jane'
    age 25
    address {
        city 'NYC'
    }
}

SQL/Database

import groovy.sql.Sql

def sql = Sql.newInstance(
    'jdbc:h2:mem:test',
    'org.h2.Driver'
)

// Query
sql.eachRow('SELECT * FROM users') { row ->
    println "${row.name}, ${row.age}"
}

// Single result
def person = sql.firstRow(
    'SELECT * FROM users WHERE id = ?',
    [1]
)

// Execute
sql.execute('''
    CREATE TABLE users (
        id INT PRIMARY KEY,
        name VARCHAR(100)
    )
''')

// Insert
sql.executeInsert(
    'INSERT INTO users VALUES (?, ?)',
    [1, 'John']
)

// Transaction
sql.withTransaction {
    sql.execute('UPDATE ...')
    sql.execute('INSERT ...')
}

Groovy vs Java

Key Differences

Feature Java Groovy
Semicolons Required Optional
Return Explicit Implicit (last expr)
Types Required Optional (def)
Properties Getters/setters Auto-generated
Strings String only GString, slashy, etc.
Collections Verbose Native syntax
Null safety Manual checks ?., ?: operators
Closures Lambdas (8+) First-class closures
Operator overload No Yes

Syntax Comparison

// Java
public class Person {
    private String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

// Groovy equivalent
class Person {
    String name
}

// Java
List<String> list = Arrays.asList("a", "b", "c");
for (String item : list) {
    System.out.println(item);
}

// Groovy
def list = ['a', 'b', 'c']
list.each { println it }

Interoperability

// Call Java from Groovy
import java.util.ArrayList

def list = new ArrayList()
list.add("item")

// Use Java libraries
import java.time.LocalDate

def today = LocalDate.now()

// Groovy class in Java
// Person.groovy compiled to Person.class
// Use normally in Java code

Tips & Gotchas

Best Practices

  • Use def for local variables - Unless type is important
  • Leverage operators - ?., ?:, *., <=> for cleaner code
  • Prefer closures - More idiomatic than traditional loops
  • Use AST transformations - @Canonical, @Immutable, etc.
  • Static compilation - Use @CompileStatic for performance-critical code
  • Named parameters - More readable than positional
  • GStrings for interpolation - But use single quotes when no interpolation needed

Common Pitfalls

// == is equals(), not identity
def a = [1, 2]
def b = [1, 2]
a == b                // true (use === for identity)

// GString vs String
def name = "World"
def map = ["Hello $name": 1]
map["Hello World"]    // null! (GString key)
// Use toString() or single quotes

// Closure vs method parameter
[1,2,3].each { it * 2 }  // Closure (ignored)
[1,2,3].collect { it * 2 }  // Returns result

// Return in closure
def list = [1,2,3].collect {
    if (it == 2) return 0  // Returns from closure
    it * 2
}  // [2, 0, 6]

// Power operator precedence
-2 ** 2               // -4 (not 4)
(-2) ** 2             // 4

Performance Tips

  • Use @CompileStatic - For performance-critical code
  • Avoid excessive metaprogramming - Runtime overhead
  • Cache compiled patterns - When using regex frequently
  • Use primitives - When possible in tight loops
  • Profile before optimizing - Groovy is usually fast enough

Also see