Errors you can handle
Throwing with a reason, catching only what you can do something about, the finally block that always runs, and the difference between a bug and a bad input.
Throw when the value cannot be used
Some values cannot be worked with, and a method that quietly limps on with them hides the mistake until it is expensive. throw new IllegalArgumentException('not a number: ' + text) says what was wrong and where — the exception carries a message, and the message is the only part a user ever sees.
Groovy does not force you to declare the exceptions a method throws, and it does not mind you throwing or catching the checked ones. That freedom is a reason to be more careful rather than less: the message should name the value, not the feeling. The type matters as much as the message: IllegalArgumentException says the caller passed something wrong, while IllegalStateException says the object was not ready, and a catch block can act on that difference.
Catch what you can handle
try { ... } catch (IllegalArgumentException e) { ... } runs the block, and if it throws something of that type, the catch block takes over with the exception as a value. e.message is the text; e.getClass().simpleName is the type, which is what you print when you are not sure what went wrong.
Catch the narrowest type that makes sense. A bare catch (Exception e) catches everything, including the programming mistakes you wanted to see, and turns each of them into the same shrug. When the block cannot do anything useful about the failure, letting it out is the honest option. A catch that only prints is a decision that the program can carry on; when it cannot, rethrow or let the exception travel to a place that can.
finally, and the two kinds of failure
A finally block runs whether the try block succeeded, failed or returned. It is where cleanup belongs: closing something, printing the last line of a report, leaving a flag off. Here it is one line of proof that the block ran.
It helps to separate two failures in your head. Bad input is expected — someone typed old where an age was wanted — so a check and a clear message is the whole fix. A bug is unexpected: an index that cannot exist, a method called on null. Catching those does not repair them; the useful move is to let the program fail where it can still tell you the line.
Throw, catch, clean up
def divide(a, b) {
if (b == 0) {
throw new ArithmeticException('cannot divide by zero')
}
a / b
}
try {
println divide(10, 2)
} catch (ArithmeticException e) {
println 'bad: ' + e.message
}
try {
divide(1, 0)
} catch (Exception e) {
println 'caught: ' + e.message
} finally {
println 'done'
}
def text = 'abc'
try {
println text.toInteger()
} catch (NumberFormatException e) {
println 'not a number: ' + text
}The exception is an object
An exception carries a type, a message, and the stack trace that says where it came from. The type decides which catch takes it; the message is the part written for a human; the stack trace is the part written for you. e.getMessage() and e.message are the same call in Groovy. e.cause is the exception that triggered this one, which is how a wrapper keeps the original failure while adding context of its own.
Catching a parent type catches its children: catch (Exception e) takes IllegalArgumentException, NumberFormatException and everything else below. That is why catch blocks go from specific to general — a general one placed first makes the specific ones unreachable, and the compiler refuses the file rather than choosing for you.
Fail at the boundary, recover where you can
An exception is a message travelling up the call stack until someone handles it. Handle it where there is enough information to do something: at the edge where input arrives, or where a fallback genuinely exists. In the middle of a calculation, letting it travel is usually better than guessing a value.
finally is for the path out, not for recovery. It runs after a success, after a failure, and after an early return, which makes it the right place to release something or print a closing line. It is the wrong place to put a return: that would replace whatever the try block was about to answer.
Three error-handling mistakes: a general catch before a specific one is a compile error, so order them narrow to wide; printing only e.message can print null, because not every exception has one — include the type; and a return inside finally replaces the value or the exception on the way out, so keep finally for cleanup.
Try these next
- Throw an
IllegalArgumentExceptionfrom a method and catch it in the caller. - Catch
NumberFormatExceptionaround'abc'.toInteger()and print the type and message. - Add a
finallythat prints a line, then add an earlyreturnand check that it still runs.
One good age, one bad one
Run it and follow which lines come from which block. Then change the guard so it rejects ages over 120 as well, and catch that case in the same try.
A safe average
Run it and count which values were kept and which were skipped. Then add a value of `'15'` to the list and predict the new total before running again.
An empty catch block is a bug with a lid on it. If there is genuinely nothing to do, say so in a comment — the next reader needs to know the silence was a decision.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does throw new IllegalArgumentException('bad: ' + text) do?
