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 anyException.- List indexes that are out of range return
nullin Groovy ([1, 2][5]isnull), so case 3 printednullinstead 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