Exceptions & Error Handling

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 (6.0.0, 2026-09-22) and every example was run with Groovy 6.0.0 on JDK 26 (checked on 2026-10-14). Exception handling in Groovy is the Java mechanism with the compile-time ceremony removed: the same try, catch and finally, the same exception hierarchy, but no obligation to declare or catch checked exceptions, an optional catch type, and a handful of runtime exceptions that exist only because Groovy dispatches calls dynamically. For the Java baseline see Java Exceptions.

try / catch / finally

Braces are required around each block, as in Java. A try can be followed by catch blocks, a finally block, or both. The value of a method is its last expression, and that holds for try and catch blocks too, so they can produce the result without an explicit return.

def parse(String s) {
    try {
        Integer.parseInt(s)
    } catch (NumberFormatException e) {
        println "bad number: ${e.message}"
        -1
    } finally {
        println "parsed '$s'"
    }
}
println parse('12')
println parse('x1')
parsed '12'
12
bad number: For input string: "x1"
parsed 'x1'
-1

Multi-catch

Since Groovy 2.0 one catch block can handle several unrelated exception types, separated by |, exactly as in Java 7.

def arr = new int[1]
[{ null.foo() }, { arr[3] }, { 'x'.toInteger() }].each { action ->
    try {
        action()
    } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
        println "multi-catch: ${e.class.simpleName}"
    } catch (NumberFormatException e) {
        println "separate: ${e.class.simpleName}"
    }
}
multi-catch: NullPointerException
multi-catch: ArrayIndexOutOfBoundsException
separate: NumberFormatException

Catching without a type

The exception type in a catch is optional. catch (e) or catch (any) is shorthand for catch (Exception e), which does not catch an Error such as StackOverflowError or AssertionError.

try { 1 / 0 } catch (e) { println "untyped: ${e.class.name}: ${e.message}" }
try { 'a'.toInteger() } catch (any) { println "any: ${any.class.simpleName}" }

def check(Closure body) {
    try { body() }
    catch (Exception e) { println "Exception: ${e.class.simpleName}" }
    catch (Throwable t) { println "Throwable: ${t.class.simpleName}" }
}
check { throw new IllegalStateException() }
check { assert false }
check { throw new StackOverflowError() }
untyped: java.lang.ArithmeticException: Division by zero
any: NumberFormatException
Exception: IllegalStateException
Throwable: PowerAssertionError
Throwable: StackOverflowError

The style guide recommends the untyped form when you really want to handle any exception. The usual advice still applies: catch the narrowest type you can act on, and do not swallow exceptions silently. A failed assert raises a PowerAssertionError, which is an Error, so a plain catch (e) lets it through (see Assertions & Power Assert).

Checked exceptions are not enforced

Groovy does not distinguish checked from unchecked exceptions at compile time. A method need not declare throws, and a caller need not catch anything. The exception still propagates as the same class, so Java code that catches it behaves normally, and you can still catch it by its type in Groovy.

def risky() { throw new IOException('disk gone') }
try { risky() } catch (IOException e) { println "caught ${e.message}" }

def caller() { risky() }          // no throws clause needed
try { caller() } catch (Exception e) { println e.class.name }
caught disk gone
java.io.IOException

The consequence is that the compiler never tells you that a call can fail. Document the exceptions in Groovydoc, and prefer unchecked exceptions for your own APIs. When a Groovy class implements a Java interface or is called from Java, the throws clause matters on the Java side: a Java caller cannot catch a checked exception that a Groovy method did not declare, unless it catches Exception.

Resource management

Groovy 3.0 and later accept Java’s try-with-resources (ARM, automatic resource management): any AutoCloseable declared in the resource list is closed automatically, in reverse order, before the catch block runs. Groovy also offers closure methods with the same effect that its documentation calls more idiomatic: withCloseable on any Closeable, and for files withReader, withWriter, withInputStream, withOutputStream and others. These methods close the resource even when the closure throws.

class Probe implements Closeable {
    String name
    void close() { println "closing $name" }
}
try (def a = new Probe(name: 'a'); def b = new Probe(name: 'b')) {
    println 'working'
    throw new IllegalStateException('boom')
} catch (IllegalStateException e) {
    println "caught ${e.message}"
}

new Probe(name: 'c').withCloseable { println "using $it.name" }

def f = File.createTempFile('greenhouse', '.txt')
f.text = 'line1\nline2\n'
println f.withReader { it.readLine() }
f.withWriter { it << 'rewritten' }
println f.text
f.delete()
working
closing b
closing a
caught boom
using c
closing c
line1
rewritten

Note that the resources are closed before the catch block runs, so the output order above is closing b, closing a, caught boom. See Files I/O & Paths for the file-oriented with methods.

Exceptions that exist because of dynamic dispatch

Because method calls and property accesses are resolved at run time, a mistake that is a compile error in Java becomes an exception in dynamic Groovy. The three most common ones are below. All are unchecked; GroovyCastException extends ClassCastException, while the other two extend groovy.lang.GroovyRuntimeException.

Exception Raised when

groovy.lang.MissingMethodException

No method matches the name and the argument types of the call (getMethod() and getType() say which)

groovy.lang.MissingPropertyException

A property or field does not exist (getProperty() names it)

org.codehaus.groovy.runtime.typehandling.GroovyCastException

A cast or typed assignment fails

import org.codehaus.groovy.runtime.typehandling.GroovyCastException

try { 'abc'.nothing() } catch (MissingMethodException e) { println "MME: ${e.method} on ${e.type.simpleName}" }
try { 'abc'.nope } catch (MissingPropertyException e) { println "MPE: ${e.property}" }
try { 'abc' as Integer } catch (NumberFormatException e) { println "as Integer: ${e.class.simpleName}" }
try { (Date) 'x' } catch (GroovyCastException e) { println "cast: ${e.message}" }
try { Integer n = 'abc' } catch (GroovyCastException e) { println "assign: ${e.message}" }

def e = new RuntimeException('outer', new IOException('inner'))
println e.cause.message
MME: nothing on String
MPE: nope
as Integer: NumberFormatException
cast: Cannot cast object 'x' with class 'java.lang.String' to class 'java.util.Date'
assign: Cannot cast object 'abc' with class 'java.lang.String' to class 'java.lang.Integer'
inner

The as Integer line shows that as delegates to a conversion method, here toInteger(), so the failure is that method’s exception rather than a cast exception. The GroovyCastException is produced by a plain cast or a typed assignment. Under static type checking the first two mistakes are compile errors instead, which is one of the main reasons to use it. Runtime hooks such as methodMissing and propertyMissing let a class intercept these cases before the exception is thrown, see Runtime Metaprogramming & the MOP.

Stack-trace sanitising

A Groovy stack trace is longer than the Java equivalent because the call goes through the dispatch machinery and closure classes. Groovy therefore provides org.codehaus.groovy.runtime.StackTraceUtils, which removes the frames of the Groovy runtime, the reflection layer and the JDK internals (by default every frame whose class starts with groovy., org.codehaus.groovy., java., javax., sun. and a few other prefixes), leaving the frames of your code. You can call StackTraceUtils.sanitize(throwable) yourself, which rewrites the stack trace of the exception and returns it.

import org.codehaus.groovy.runtime.StackTraceUtils

def t = [{ throw new IllegalStateException('x') }].collect { c ->
    try { [1].each { c() } } catch (e) { e }
}[0]
println "raw frames: ${t.stackTrace.size()}"
StackTraceUtils.sanitize(t)
println "sanitized frames: ${t.stackTrace.size()}"
println t.stackTrace.take(3)*.toString().join('\n')
raw frames: 31
sanitized frames: 5
b5$_run_closure1.doCall(b5.groovy:3)
b5$_run_closure1.doCall(b5.groovy)
b5$_run_closure2$_closure3.doCall(b5.groovy:4)

The class names in the last lines come from the script being saved as b5.groovy, and the line numbers count the import and the blank line after it: the closure that throws is on line 3 and the each call on line 4. The groovy command has a debug switch, -d (--debug), which prints the full unsanitised stack trace of an uncaught exception. For a three-method script that threw an exception, the plain run printed 25 lines and the run with -d 37 lines, the difference being the Groovy launcher frames that the plain run filters out of the trace it prints last. Use -d when the sanitiser hides the frame that you need, for example when diagnosing a failure inside the Groovy runtime. In your own application the system property groovy.full.stacktrace switches the sanitising off, and groovy.sanitized.stacktraces replaces the list of filtered package prefixes. See Running Scripts & the groovy Command for the command line.

Where Groovy 6.0 and older books differ

Books and older tutorials describe the pre-3.0 situation: no multi-catch before 2.0, no Java-style try-with-resources before 3.0, and withCloseable and the with* family as the only resource idiom. Those closure methods remain valid and idiomatic. A failed assert raises PowerAssertionError, a subclass of AssertionError, so catching AssertionError handles it. The semantics of untyped catch, unchecked treatment of checked exceptions and the dynamic exceptions above have not changed.