Skip to content
AgileLogics
6 min read

Exceptions and Error Handling

Prerequisites: Classes and objects

Groovy uses Java's exceptions with try, catch, and finally, with one big difference: there are no checked exceptions. You never have to declare throws or catch an exception just to satisfy the compiler.

def parseAge(String text) {
    if (!text?.isInteger()) throw new IllegalArgumentException("not a number: '$text'")
    def age = text.toInteger()
    if (age < 0) throw new IllegalArgumentException('age cannot be negative')
    age
}

['42', 'abc', '-5', null].each { input ->
    try {
        println "age = ${parseAge(input)}"
    } catch (IllegalArgumentException e) {
        println "error: $e.message"
    } finally {
        println '  (checked)'
    }
}
age = 42
  (checked)
error: not a number: 'abc'
  (checked)
error: age cannot be negative
  (checked)
error: not a number: 'null'
  (checked)

finally always runs — whether the try succeeded or not — so it's the place for cleanup.

Catching several types

def risky(int n) {
    switch (n) {
        case 1 -> { throw new FileNotFoundException('config.yml') }
        case 2 -> { throw new ArithmeticException('divide by zero') }
        case 3 -> [1, 2][5]                 // IndexOutOfBoundsException
        default -> 'fine'
    }
}

(0..3).each { n ->
    try {
        println risky(n)
    } catch (IOException | ArithmeticException e) {
        println "known problem: ${e.class.simpleName}: $e.message"
    } catch (e) {
        println "unexpected: ${e.class.simpleName}"
    }
}
fine
known problem: FileNotFoundException: config.yml
known problem: ArithmeticException: divide by zero
null
  • catch (A | B e) handles several types in one block.
  • catch (e) with no type catches any Exception.
  • List indexes that are out of range return null in Groovy ([1, 2][5] is null), so case 3 printed null instead of failing.

Custom exceptions

class InsufficientFundsException extends RuntimeException {
    final BigDecimal shortfall
    InsufficientFundsException(BigDecimal shortfall) {
        super("short by $shortfall")
        this.shortfall = shortfall
    }
}

try {
    throw new InsufficientFundsException(12.50)
} catch (InsufficientFundsException e) {
    println "$e.message (shortfall: $e.shortfall)"
}
short by 12.50 (shortfall: 12.50)

Extend RuntimeException (or a more specific one) and add fields for details the caller needs.

Resources that close themselves

class Connection implements Closeable {
    String name
    void close() { println "closing $name" }
}

try (def c = new Connection(name: 'db')) {
    println "using $c.name"
}

new Connection(name: 'cache').withCloseable { c ->
    println "using $c.name"
}
using db
closing db
using cache
closing cache

Java's try-with-resources works, and Groovy's withCloseable, withReader, and withWriter do the same with a closure.

Power assertions

def order = [items: [3, 4], discount: 2]
try {
    assert order.items.sum() - order.discount == 7
} catch (AssertionError e) {
    println e.message
}
assert order.items.sum() - order.discount == 7
       |     |     |     | |     |        |
       |     |     7     5 |     2        false
       |     [3, 4]        ['items':[3, 4], 'discount':2]
       ['items':[3, 4], 'discount':2]

Groovy's assert is always enabled and prints the value of every sub-expression when it fails — a power assertion. It's ideal for scripts and tests.

Practice

Write a method safeDivide(a, b) that returns a / b, or the string 'undefined' when b is zero, by catching the exception.

Show solution
def safeDivide(a, b) {
    try {
        a / b
    } catch (ArithmeticException e) {
        'undefined'
    }
}

println safeDivide(10, 4)
println safeDivide(1, 0)
2.5
undefined