Groovy — Reference

Source: https://groovy-lang.org/documentation.html

Groovy

  • Created: 2003 by James Strachan; first stable release 1.0 in 2007; under Apache Software Foundation since 2015
  • Latest stable: Groovy 5.0.x (5.0.5 series shown in current docs dropdown); Groovy 4.x is the prior stable line. (groovy-lang.org/documentation.html)
  • Paradigms: Object-oriented, imperative, functional (closures), scripting; optional static typing
  • Typing: Dynamic by default, static via @CompileStatic / @TypeChecked; gradual / optional typing model
  • Memory: JVM-managed (whatever GC the host JVM uses)
  • Compilation: Compiled to JVM bytecode by groovyc; can run as scripts (compiled on the fly) or as .class files; AOT or JIT via the JVM
  • Primary domains: JVM scripting, build tooling (Gradle DSL — predates Kotlin DSL), test DSLs (Spock), web frameworks (Grails), CI pipelines (Jenkins shared libraries), application configuration as code
  • Notable runtimes: Reference Apache Groovy on any Java 11+ JVM (Groovy 5 needs JDK 17 to build, runs on JDK 11+)
  • Official docs: https://groovy-lang.org/documentation.html

At a glance

Groovy is “Java with the boring parts removed and superpowers added.” Source compiles to standard JVM bytecode; Groovy classes interop bidirectionally with Java classes (a Groovy class is a real java.lang.Class). The headline features: closures with implicit it and configurable delegate strategy, an extensive GDK that adds methods to String, Collection, File, etc., builders that lean on closure delegation to produce nested DSLs (the foundation of Gradle), runtime metaprogramming via MetaClass, and compile-time metaprogramming via AST transformations (@ToString, @EqualsAndHashCode, @CompileStatic, @Singleton, @Immutable). Groovy 5.0 added support up to JDK 25, JEP 512 compact source files, instance-main, and pattern matching for instanceof. (Groovy 5 release notes)

Getting started

Install:

  • SDKMAN (canonical): sdk install groovy, switch with sdk use groovy 5.0.5. SDKMAN is the de facto version manager.
  • Homebrew: brew install groovy
  • Manual: download from https://groovy.apache.org/download.html, set GROOVY_HOME, add $GROOVY_HOME/bin to PATH. Requires JDK 11+ (5.x).

Hello world:

// hello.groovy
println "Hello, world!"

Run as a script (no class needed): groovy hello.groovy. Or wrap in a class:

class App {
    static void main(String[] args) { println "Hello!" }
}

Project layout: Driven by Gradle, the canonical build tool (which is itself written in / configured with Groovy):

src/main/groovy/com/acme/App.groovy
src/test/groovy/com/acme/AppSpec.groovy   // Spock specs
build.gradle                               // Groovy DSL (or Kotlin DSL)
settings.gradle

Package/build tool:

  • Gradle is the dominant build tool. gradle init --type groovy-application.
  • Maven with gmavenplus-plugin works fine.
  • Grape (@Grab) for inline dependency declarations in scripts: @Grab('org.apache.commons:commons-lang3:3.14.0') resolves and adds to classpath at runtime via Ivy.

REPL: groovysh — Groovy 5 ships a JLine-3 rebuild with syntax highlighting, completion, and history. (release notes) groovyConsole is a Swing GUI with an AST browser (huge for understanding what AST transforms emit).

Basics

Types & literals. (syntax doc)

  • Integers: byte, short, int, long, BigInteger. Underscore separators allowed: 1_000_000. Bases: 0b1010, 0xFF, 010 (octal).
  • Decimals: float, double, BigDecimal is the default for decimal literals (3.14 is a BigDecimal, not a double — a famous gotcha for Java refugees).
  • Strings: single-quoted 'plain' (no interpolation), double-quoted "hello $name" (GString with interpolation), triple-quoted multi-line, slashy /regex \d+/, dollar-slashy $/...$ uses different escapes/$.
  • Lists: [1, 2, 3] is an ArrayList. Maps: [a: 1, b: 2] is a LinkedHashMap (literal keys are auto-quoted; use [(varName): val] to use a variable as a key).
  • Booleans: “Groovy truth” — non-null, non-empty, non-zero, non-false-Boolean is true. if (collection) {…} is true iff non-empty.

Variables & scoping.

def x = 42                  // dynamic
int y = 42                  // typed
final z = "constant"
var w = 3.14                // since Groovy 3 (Java-style var)

Lexical scoping inside methods/closures. Scripts have a Binding that holds top-level vars accessible across the script.

Control flow.

if (x > 0) ... else if ... else ...
switch (val) {
    case Integer:        ...; break  // type matching
    case ~/\d+/:         ...; break  // regex matching
    case [1, 2, 3]:      ...; break  // collection membership
    case { it > 100 }:   ...; break  // closure predicate
    default:             ...
}
for (item in list) { ... }
list.each { item -> ... }    // closure-based
list.eachWithIndex { item, i -> ... }

Functions / closures.

def add(int a, int b = 0) { a + b }     // last expression is the return value
Closure square = { x -> x * x }
Closure increment = { it + 1 }          // implicit `it` parameter
[1,2,3].collect(square)                  // method-reference-like

Closures capture lexical scope and have a configurable delegate — the foundation of every Groovy DSL.

Strings. GStrings interpolate lazily — "${expensiveCall()}" is evaluated when the GString is toString()’d. == does equals() (not reference equality — another Java-refugee trap; use is() for identity). Methods like padLeft, center, * (repeat), tokenize.

Collections. GDK augments the JDK: collect, findAll, find, inject (fold), groupBy, countBy, sort, unique, sum, min/max, take/drop/takeWhile/dropWhile, *. (spread-dot — users*.name extracts every name), ?. (safe-nav), ?: (Elvis).

Intermediate

Type system depth. Three modes:

  1. Dynamic (default): every method call goes through MOP (MetaClass); flexible but slower and fails at runtime.
  2. @TypeChecked: compile-time type checking with full static rules; calls still dispatched dynamically.
  3. @CompileStatic: type-checked AND statically compiled — bytecode equivalent to Java for the typed parts; ~10x faster, no metaclass tricks for those methods. Use on hot paths and library APIs.

Modules. Java-style packages (package com.acme); imports include defaults (java.lang.*, java.util.*, java.io.*, java.net.*, groovy.lang.*, groovy.util.*, java.math.BigInteger, java.math.BigDecimal). JPMS supported.

Error handling. Java-style try/catch/finally. Multi-catch catch (IOException | SQLException e). Checked exceptions exist syntactically but are not enforced by Groovy — you can throw a checked exception without declaring it. try-with-resources works on AutoCloseable.

Concurrency. All JVM concurrency primitives: Thread, ExecutorService, CompletableFuture, java.util.concurrent.*, synchronized. GPars library adds actors, dataflow, parallel collections (list.collectParallel), agent (Clojure-style). @Synchronized AST transform generates a private lock + sync block.

I/O. new File(path).text reads everything. file.eachLine { ... }, file.withReader { r -> ... } (closes for you), file << "appended\n", file.withWriter, Process extensions: "ls -la".execute().text. XmlSlurper/XmlParser for XML, JsonSlurper/JsonOutput for JSON.

Stdlib highlights. groovy.json.*, groovy.xml.*, groovy.sql.Sql (concise JDBC wrapper), groovy.text.SimpleTemplateEngine/StreamingTemplateEngine, groovy.transform.* (the AST-transform pack), groovy.util.ConfigSlurper (typed config files), groovy.cli.commons.CliBuilder/groovy.cli.picocli.CliBuilder (CLI parsing).

Advanced

Memory / GC. Whatever the JVM provides (G1 default since Java 9, ZGC for low pause). Closures retain enclosing scope — a closure stored in a long-lived collection holds references to its captured locals; classic memory leak source.

Concurrency deep dive. @CompileStatic + j.u.c.* is the production pattern. GPars Actors give Erlang-style mailbox concurrency on the JVM. Dataflow variables (def x = new DataflowVariable(); x << compute()) for declarative parallelism. Parallel array methods (list.makeConcurrent().collect{…}).

FFI. No special FFI; use the JVM’s: JNI (manual C glue), JNA (declarative), Project Panama / java.lang.foreign (since JDK 22) — all callable directly from Groovy with the same syntax as Java. Groovy is also commonly the FFI: embed a GroovyShell in a Java app to evaluate user-supplied scripts.

Reflection. Java reflection works (obj.class.methods). Groovy adds MetaClass access: String.metaClass.shout = { -> delegate.toUpperCase() }; "hi".shout() adds an instance method at runtime. obj.metaClass.getMetaMethods(), respondsTo(), hasProperty(). The ExpandoMetaClass lets you mutate any class’s MOP dynamically — testing/mocking superpower.

Performance tools. JFR / async-profiler / VisualVM all work on Groovy bytecode. Use groovyc -d build to inspect emitted bytecode with javap -c. @CompileStatic is the single biggest win — typically 5-10x for arithmetic-heavy code. Avoid GString creation in hot paths (allocates per call); pre-build String.format patterns.

God mode

  • AST Transformations. Two flavors. (1) Local annotations like @ToString, @EqualsAndHashCode, @TupleConstructor, @Builder, @Immutable, @Memoized, @Singleton, @Sortable, @Delegate, @Lazy, @Newify — apply at compile time, generate methods/constructors. (2) Global transforms via @GroovyASTTransformation that rewrite arbitrary AST. Build by implementing org.codehaus.groovy.transform.ASTTransformation and registering in META-INF/services. Inspect what they emit using groovyConsole → Script → Inspect AST.

  • @CompileStatic + @TypeChecked(extensions=...). Custom static-type-checker extensions let you build DSL-aware type checkers (e.g., @TypeChecked(extensions = 'sql-typechecker.groovy') validates SQL string types at compile time).

  • GroovyShell / GroovyClassLoader. Runtime evaluation: new GroovyShell().evaluate("1+1"). Configure with CompilerConfiguration to inject AST transforms, default imports, base script class, security policy. Pattern: an app exposes its API as a Script base class; user scripts get DSL methods for free.

  • Closure delegate strategies. Every closure has delegate, owner, thisObject. delegate strategy can be OWNER_FIRST (default), DELEGATE_FIRST, OWNER_ONLY, DELEGATE_ONLY. Set closure.resolveStrategy = Closure.DELEGATE_FIRST and unqualified names resolve against delegate first — the trick that makes task('build') { dependsOn 'compile' } look like syntax in Gradle.

  • Builder pattern (DSL fundamentals). MarkupBuilder, JsonBuilder, SwingBuilder, XmlBuilder use methodMissing + propertyMissing + closure delegation:

    new MarkupBuilder().html { head { title 'Hi' }; body { p 'Hello' } }
  • ExpandoMetaClass + categories. Add methods at runtime to any class: Number.metaClass.squared = { delegate * delegate }; 5.squared(). Categories scope the change to a use block: use(StringCategory) { "hi".doSomething() }. The Spock framework leans on this for Mock/Stub.

  • Mixins via metaclass. class Foo { }; Foo.metaClass.mixin(SomeMixin) injects all methods of SomeMixin into Foo’s MOP at runtime. AST-time alternative: @Mixin(SomeMixin).

  • Bytecode emission. groovyc -d out src/Foo.groovy, then javap -c -p out/Foo.class. Use @CompileStatic to see how close emitted code is to handwritten Java (very close).

  • Grape internals. @Grab resolves through Ivy at script start; cache lives at ~/.groovy/grapes/. groovy.grape.Grape.grab([group:'…', module:'…', version:'…']) is the programmatic API.

Idioms & style

  • Naming: lowerCamelCase for methods/vars, UpperCamelCase for classes, UPPER_SNAKE for constants — same as Java.
  • def vs explicit type: prefer types in production code (especially with @CompileStatic); def is fine in scripts and tests.
  • Use == for equality, is() for identity. Opposite of Java; this trips everyone.
  • Prefer each/collect/findAll over for for clarity (and trivially parallelizable later).
  • @Immutable for value classes. Generates fields, equals/hashCode/toString, defensive copies — Groovy’s analog of Java records, predates them.
  • Builder closures over constructor args for >3 params.
  • Formatter/linter: CodeNarc is the standard linter (Checkstyle-equivalent for Groovy); IntelliJ IDEA’s Groovy plugin has the only mature formatter.
  • Keep dynamic code in DSL surfaces, not hot paths. @CompileStatic everything that’s not a DSL.

Ecosystem

  • Build: Gradle (Groovy DSL is Groovy code) — Android, Spring Boot, most JVM polyglots default to it.
  • Testing: Spock Framework — given/when/then BDD DSL on Groovy with AST-rewritten assertion failures that show every subexpression. Industry standard for JVM testing in many shops. JUnit 5 + Groovy also fine.
  • Web framework: Grails (Rails-inspired, Spring Boot under the hood, GORM ORM with dynamic finders).
  • Scripting / orchestration: Jenkins shared libraries are Groovy; CodeNarc, Geb (browser automation, Selenium wrapper).
  • Data: groovy.sql.Sql for JDBC, GORM (in Grails) for ORM.
  • Notable users: Gradle Inc., Netflix (Spinnaker uses Groovy), LinkedIn (test infrastructure), Jenkins / CloudBees (pipelines), many financial firms for trading-rule DSLs.

Gotchas

  • Decimal literals are BigDecimal, not double. 0.1 + 0.2 == 0.3 is true in Groovy — but mixing with Java doubles causes silent boxing/unboxing perf hits.
  • == is equals(), not == from Java. Use is() for identity. JavaScript-like.
  • GString vs String. def s = "hi $name" is a GString. Map keys, hashCodes, and equals against String can surprise: ["hi $name": 1].containsKey("hi bob") is false — the GString hashes lazily. Force with .toString().
  • Checked exceptions silently bypass. Calling Java code that throws checked exceptions doesn’t require declaration — fine for scripts, dangerous in mixed Groovy/Java codebases.
  • Closure capture leaks. A closure stored in a static field captures its enclosing scope; classloader leaks in app servers.
  • Default parameter classes are loaded for def. If you use def x in @CompileStatic, you opt back into dynamic dispatch for that variable; defeats the optimization.
  • it shadowing. Nested closures all use it; inner it shadows outer. Name parameters explicitly when nesting.
  • Map literal vs labelled-statement ambiguity. [a: 1] is a map. a: at statement position can be parsed as a label. Rare but surprising.
  • Grape behind a corporate proxy needs ~/.groovy/grapeConfig.xml Ivy settings; a Grape failure is a script failure with a confusing stack trace. Pre-resolve with Maven or set up a Nexus proxy.
  • Groovy 5 needs JDK 17 to build, JDK 11 to run. Some older Java environments need an upgrade. (release notes)
  • @CompileStatic removes meta-programming for that scope — someStr.metaClass.foo = … won’t take effect on @CompileStatic callers.

Citations