Assertions & Power Assert
|
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). Groovy’s assert is a statement, not a library call, and it behaves differently from the Java
assert keyword in two ways that matter in practice: a failure prints the value of every sub-expression (the power assert),
and the check is always executed, whatever the JVM flags say. Those two properties are why Groovy code, including the other
pages of this section, uses assert as an executable form of documentation.
The assert statement
assert takes a condition, evaluated with the Groovy Truth (see
Booleans & the Groovy Truth), and optionally a message
after a colon or a comma. When the condition is false it throws; when it is true nothing happens.
assert 1 + 1 == 2 // plain boolean
assert 'basil' // non-empty string is true
assert [1, 2] // non-empty collection is true
assert !'' // empty string is false
assert !([] as boolean)
assert 0 == 0.0 // == compares numbers by value
assert null?.size() == null
def n = 0
assert ++n == 1 // the expression is really evaluated: n is now 1
assert n == 1
println 'all passed'
all passed
The condition can be any expression: a method call, a regular expression match ('a1' ==~ /\d+/), a closure call or a
collection predicate such as readings.every { it.t < 25 }.
The power-assert output
When the condition is false, Groovy re-renders the source text of the condition and, under it, draws the value of every sub-expression that was evaluated, each connected to its operator or member by a vertical bar. Reading the diagram bottom-up follows evaluation order.
def temps = [18, 21, 25]
def limit = 24
try {
assert temps.max() < limit
} catch (AssertionError e) {
println e.message
}
println '---'
def plant = [name: 'basil', zone: [id: 7]]
try {
assert plant.zone.id == 3 && plant.name.size() == 5
} catch (AssertionError e) {
println e.message
}
println '---'
String name = null
try {
assert name?.size() > 0
} catch (AssertionError e) {
println e.message
}
println '---'
def a = [1, 2, 3]
def b = [1, 2, 4]
try {
assert a == b
} catch (AssertionError e) {
println e.message
}
assert temps.max() < limit
| | | |
| 25 | 24
| false
[18, 21, 25]
---
assert plant.zone.id == 3 && plant.name.size() == 5
| | | | |
| | 7 | false
| | false
| ['id':7]
['name':'basil', 'zone':['id':7]]
---
assert name?.size() > 0
| | |
null null false
---
assert a == b
| | |
| | [1, 2, 4]
| false
[1, 2, 3]
What the diagrams show:
-
Each vertical bar points at an operator, method call or property access; the value under it is the result of that operation. In the first case
temps.max()produced25,limitis24, and<producedfalse. -
In the second case the left operand of
&&is alreadyfalse, so the right operandplant.name.size() == 5was never evaluated and has no value drawn: the diagram reflects short-circuiting. -
The third case shows why the output is useful with the safe-navigation operator: both
nameandname?.size()arenull, so the comparison with0isfalse. -
Values are rendered with their Groovy
inspect()-like form, so strings are quoted and maps keep their key quotes. That is what lets you see trailing spaces or the difference between'1'and1.
A failing expression with a closure shows the same structure; the closure body is not expanded, only the value of the call that contains it.
def readings = [[id: 's1', t: 21.5], [id: 's2', t: 29.0]]
try {
assert readings.every { it.t < 25 }
} catch (AssertionError e) {
println e.message
}
try {
assert readings*.t.sum() / readings.size() == 25
} catch (AssertionError e) {
println e.message
}
assert readings.every { it.t < 25 }
| |
| false
[['id':'s1', 't':21.5], ['id':'s2', 't':29.0]]
assert readings*.t.sum() / readings.size() == 25
| | | | | | |
| | 50.5 | | 2 false
| | | [['id':'s1', 't':21.5], ['id':'s2', 't':29.0]]
| | 25.25
| [21.5, 29.0]
[['id':'s1', 't':21.5], ['id':'s2', 't':29.0]]
Assertion messages
An assertion can carry a message, written after a colon or after a comma. The message is any expression, so a GString works; its text is appended to the failure, followed by the original expression and the values of the simple variables involved. The two forms are equivalent. Note the change of format: with a message, the multi-line diagram is replaced by a single line.
def limit = 24
try {
assert limit == 25 : "limit should be 25, was ${limit}"
} catch (AssertionError e) {
println e.message
}
try {
assert limit == 25, 'limit mismatch'
} catch (AssertionError e) {
println e.message
}
try {
assert false
} catch (AssertionError e) {
println e.message
}
try {
assert null, 'null value'
} catch (AssertionError e) {
println e.message
}
def calls = 0
def expensive = { calls++; 'computed message' }
assert true : expensive()
println "calls after a passing assert: ${calls}"
try {
assert false : expensive()
} catch (AssertionError e) {
println "${e.message} (calls: ${calls})"
}
limit should be 25, was 24. Expression: (limit == 25). Values: limit = 24
limit mismatch. Expression: (limit == 25). Values: limit = 24
assert false
null value. Expression: null
calls after a passing assert: 0
computed message. Expression: false (calls: 1)
Practical consequences:
-
Because the message expression is evaluated only when the assertion fails, it can be expensive or have side effects without cost on the passing path (see
callsabove). -
Put the information the reader needs (the offending id, the expected value) into the message; the power-assert diagram is replaced by
Expression: ...and aValues:list that contains only simple variables, not method-call results. -
A plain message is worthwhile for checks inside methods, where the failing line alone says little.
class Zone {
int capacity
void check() {
assert capacity > 0 : "capacity must be positive: ${capacity}"
}
}
try {
new Zone(capacity: 0).check()
} catch (AssertionError e) {
println e.message
}
capacity must be positive: 0. Expression: (capacity > 0). Values: capacity = 0
The error type
A failed assertion throws java.lang.AssertionError, an Error and not an Exception. The power-assert form throws the
subclass org.codehaus.groovy.runtime.powerassert.PowerAssertionError; the message form throws a plain AssertionError.
Because it is an Error, a catch (Exception e) does not catch it, and a script that lets it escape ends with a non-zero
exit code. Catch AssertionError (or Throwable) when you need to inspect it, as the examples on this page do.
try { assert 1 == 2 } catch (AssertionError e) { println e.class.name }
try { assert 1 == 2 : 'with message' } catch (AssertionError e) { println e.class.name }
println(AssertionError.superclass.name)
println(new org.codehaus.groovy.runtime.powerassert.PowerAssertionError('x').class.superclass.name)
try {
try { assert false } catch (Exception e) { println 'not reached' }
} catch (Error e) {
println "escaped catch (Exception): ${e.class.simpleName}"
}
org.codehaus.groovy.runtime.powerassert.PowerAssertionError
java.lang.AssertionError
java.lang.Error
java.lang.AssertionError
escaped catch (Exception): PowerAssertionError
Source: Groovy API: PowerAssertionError
Assertions are always on
The Java assert keyword is disabled by default and needs java -ea to run. The Groovy statement has no such switch: the
compiler generates the check unconditionally, so a script runs its assertions with a plain groovy script.groovy. Even
-da does not turn them off, which is why asserts in Groovy can be relied on for validation and why they should never carry
needed side effects.
$ cat check.groovy
assert 1 + 1 == 3
$ groovy check.groovy
Assertion failed:
assert 1 + 1 == 3
| |
2 false
at check.run(check.groovy:1)
$ echo $?
1
$ JAVA_OPTS=-da groovy always-on.groovy # assert false : 'always on'
java.lang.AssertionError: always on. Expression: false
(The first lines of stderr in 6.0.0 also include a SEVERE: Caught: ... header from the JDK logger and a longer stack trace,
elided here.) A script whose assertions all pass prints nothing and exits with status 0.
Differences with the Java keyword, side by side:
| Aspect | Java assert |
Groovy assert |
|---|---|---|
Enabled by default |
no, needs |
yes, cannot be disabled |
Failure output |
|
the expression with every sub-expression value |
Message syntax |
|
|
Condition type |
|
any expression, evaluated with the Groovy Truth |
Typical use |
internal invariants |
invariants, documentation and tests |
Because the condition always runs, an assertion in production code is a real check with a real cost, and an AssertionError
that escapes is an Error that no ordinary catch (Exception) handles. For validating input that may legitimately be wrong
at runtime, throw an exception instead; see Exceptions & Error Handling.
Design-by-contract annotations (@Requires, @Ensures) are covered in
Design by Contract with Groovy Contracts.
Assertions as executable documentation
Because a passing assertion is silent and a stale one fails loudly, an assert next to a statement documents its behavior
and keeps the documentation correct. A script of assertions is the smallest possible specification: it reads like a table of
facts and runs like a test. Every GDK method page in this section uses the idiom; the same applies to your own code.
// What the greenhouse helpers promise, stated as facts
def celsiusToFahrenheit = { BigDecimal c -> c * 9 / 5 + 32 }
assert celsiusToFahrenheit(0) == 32
assert celsiusToFahrenheit(21.5) == 70.7
def readings = [18.2, 21.5, 24.9, 19.0]
assert readings.max() == 24.9
assert readings.findAll { it > 20 } == [21.5, 24.9]
assert readings.collect { it.round() } == [18, 22, 25, 19]
assert readings.sum() / readings.size() == 20.9
assert readings.sort(false) == [18.2, 19.0, 21.5, 24.9]
assert readings.first() == 18.2 // sort(false) left the original untouched
assert 'north-7'.tokenize('-') == ['north', '7']
assert 'north-7'.split('-').toList() == ['north', '7']
assert 'Zone'.padRight(6, '.') == 'Zone..'
println 'documentation still true'
documentation still true
When one of these facts stops being true after an upgrade of Groovy or a refactoring, the failure points at the line and shows the actual values, which is exactly the information a changelog entry would otherwise have to convey.
Asserting that something fails
An assertion states what is true; to state that an operation must throw, use shouldFail from groovy.test.GroovyAssert
(in the groovy-test module, included in the Groovy distribution). It runs a closure, fails if no exception is thrown, and
returns the exception so that it can be checked in turn.
import static groovy.test.GroovyAssert.shouldFail
def e = shouldFail(ArithmeticException) { 1 / 0 }
assert e.message.contains('zero')
def e2 = shouldFail { // any Throwable
'basil'.toInteger()
}
assert e2 instanceof NumberFormatException
try {
shouldFail { 'no exception here' }
} catch (AssertionError ae) {
println ae.message.replaceFirst(/^Closure \S+/, 'The closure')
}
The closure should have failed
Both GroovyAssert and the surrounding unit-test frameworks (JUnit, Spock) build on the same power assert; see
Testing with JUnit & GroovyTest and
Testing with Spock & Geb. For the Java side of
testing, see Java Testing.
Where the book is outdated
The Manning book Groovy in Action (publisher page in the references) introduces assert early and uses it throughout
as the way to show that an example works; that approach is unchanged and is the one this section follows. What to check
against the current documentation instead of the book:
-
The failure output. Current Groovy versions print the power-assert diagram shown above for every failing condition, with a value under each sub-expression; compare it with what the book’s listings show for a failing assertion.
-
The version and platform: the examples here need Groovy 6.0 on JDK 17 or later.
-
Where to turn for test-specific assertions:
GroovyAssert, JUnit and Spock, rather than hand-written checks; see Testing with JUnit & GroovyTest.
For the neighbouring topics see Control Structures and
Differences with Java. Other languages on this site assert
differently: Python Testing uses assert with a rewriting test runner
(pytest) for similar diagrams, while Kotlin’s assert is disabled unless -ea is set, like Java.