Groovy’s if, while and for look like Java’s, but two features change how you write conditions: Groovy truth and a far more capable switch.
Groovy truth
In Java, a condition must be a boolean. In Groovy, any value can be a condition:
| Value | Counts as |
|---|---|
null | false |
0, 0.0 | false |
'' (empty string) | false |
[], [:] (empty list/map) | false |
false | false |
| anything else | true |
def customer = ''
if (!customer) {
println 'No customer given'
}
if (orders) { ... } // "if there are any orders"
This replaces if (orders != null && !orders.isEmpty()) with if (orders).
Watch out: 0 is false. If zero is a legitimate value (a quantity, a balance), compare explicitly: if (qty != null).
The Elvis operator
def name = customer ?: 'Guest'
“Use customer if it’s true (by Groovy truth), otherwise 'Guest'” — the everyday way to apply defaults.
A switch that matches almost anything
def describe(value) {
switch (value) {
case null -> 'nothing'
case Integer -> "a number: $value" // type
case ~/[A-Z]{2}-\d{3}/ -> "a SKU: $value" // regular expression
case ['NEW', 'OPEN'] -> 'an open status' // one of these values
case { it instanceof List && it.size() > 2 } -> 'a long list' // any condition
default -> "something else: $value"
}
}
Groovy’s switch uses each case’s isCase method: types check instanceof, patterns check matches, collections check membership, ranges check bounds, and closures run as conditions. The arrow form (Groovy 4) returns a value with no fall-through; the classic case x: … break form works too.
Loops
for (sku in ['AX-200', 'BX-310']) println sku // for-in
3.times { i -> println i } // 0, 1, 2
1.upto(3) { println it } // 1, 2, 3
while (n < 2) { n++ }
['a', 'b'].eachWithIndex { v, i -> println "$i: $v" }
In idiomatic Groovy, most loops become collection methods like each, collect and findAll — see Functional collection methods.
The full example
// control.groovy — if, switch, loops and "Groovy truth".
// Groovy truth: these all count as false in a condition
def falsy = [null, 0, '', [], [:], false]
falsy.each { value -> assert !value }
println 'null, 0, empty string, empty list, empty map and false are all "false"'
def customer = ''
if (!customer) {
println 'No customer given' // runs: empty string is false
}
// Elvis operator: value if it's "true", otherwise a default
def name = customer ?: 'Guest'
println name // Guest
// switch is very flexible in Groovy
def describe(value) {
switch (value) {
case null -> 'nothing'
case Integer -> "a number: $value"
case ~/[A-Z]{2}-\d{3}/ -> "a SKU: $value"
case ['NEW', 'OPEN'] -> 'an open status'
case { it instanceof List && it.size() > 2 } -> 'a long list'
default -> "something else: $value"
}
}
['AX-200', 42, 'NEW', [1, 2, 3], null, 'hello'].each { println describe(it) }
// Loops
for (sku in ['AX-200', 'BX-310']) println "for-in: $sku"
3.times { i -> println "times: $i" }
1.upto(3) { println "upto: $it" }
def n = 0
while (n < 2) { println "while: ${n++}" }
Try it yourself
Write shippingFor(order) where order is a map: return 'free' for totals of 100 or more, 'express' if order.priority is set, otherwise 'standard' — and 'unknown' if the order is null.
Show a solution
def shippingFor(Map order) {
if (!order) return 'unknown'
if (order.total >= 100) return 'free'
order.priority ? 'express' : 'standard'
}
assert shippingFor(null) == 'unknown'
assert shippingFor([total: 150]) == 'free'
assert shippingFor([total: 20, priority: true]) == 'express'
assert shippingFor([total: 20]) == 'standard'
Key takeaways
- null, 0, empty strings, empty collections and false are all “false” in conditions.
a ?: bgives a default.switchcan match types, regexes, collections, ranges and closures.- Prefer collection methods over manual loops in idiomatic Groovy.