Groovy assert and power assert with 12 examples. Learn assertion messages, power assert output, and testing patterns. Complete guide for Groovy 5.x.
“Assertions are the developer’s safety net – they catch bugs at the exact moment assumptions break, not three stack frames later when everything is already on fire.”
Bertrand Meyer, Object-Oriented Software Construction
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 17 minutes
Every developer makes assumptions. You assume a list isn’t empty, a value is positive, a string matches a pattern. Most of the time those assumptions hold. But when they don’t, you get bugs – subtle, hard-to-trace bugs that show up far from where the actual problem started.
Groovy’s assert keyword lets you codify those assumptions and catch violations immediately. And Groovy’s power assert feature takes it further – when an assertion fails, it shows you the value of every sub-expression in the assertion, making the cause immediately obvious.
This post covers everything about Groovy assert and power assert with 12 tested examples. We’ll look at basic assertions, custom messages, power assert output, collection assertions, and how assert integrates with testing. If you’re also working with pattern matching, check out our Groovy Regular Expressions guide – assert and regex are a powerful combination for validation.
Every example is tested on Groovy 5.x with exact output shown. Let’s get into it.
Table of Contents
What is Groovy Assert?
The assert statement evaluates a boolean expression. If the expression is true (or Groovy-truthy), execution continues silently. If it’s false (or Groovy-falsy), Groovy throws a PowerAssertionError with detailed diagnostics.
Unlike Java’s assert keyword (which must be enabled with the -ea JVM flag), Groovy’s assert is always enabled. You can’t turn it off. This makes it reliable for validation – it will always catch the problem, in scripts, tests, and production code alike.
According to the official Groovy documentation, power assert was introduced in Groovy 1.7 and has been a flagship feature ever since.
Key Points:
assertis always enabled in Groovy – unlike Java, there’s no-eaflag needed- Uses Groovy truth –
null, empty strings, empty collections, and0are all falsy - Throws
PowerAssertionError(extendsAssertionError) on failure - Supports custom error messages with the
assert expression : "message"syntax - Power assert output shows the value of every sub-expression automatically
Power Assert – Groovy’s Secret Weapon
Power assert is what makes Groovy’s assert special. When an assertion fails, Groovy doesn’t just say “assertion failed.” It shows you a visual representation of the expression with the value of each part displayed below it. Here’s what that looks like:
Power Assert in Action
// This assertion passes - no output
assert 2 + 3 == 5
println "Basic assert passed silently"
// Here is what a FAILED power assert looks like
// (We'll catch it to show the message)
try {
def name = "Groovy"
assert name.length() == 5
} catch (AssertionError e) {
println "Power Assert Output:"
println e.message
}
println ""
// More complex expression
try {
def list = [1, 2, 3, 4, 5]
assert list.size() == 3 && list.contains(6)
} catch (AssertionError e) {
println "Complex Power Assert:"
println e.message
}
Output
Basic assert passed silently
Power Assert Output:
assert name.length() == 5
| | |
| 6 false
Groovy
Complex Power Assert:
assert list.size() == 3 && list.contains(6)
| | |
| 5 false
[1, 2, 3, 4, 5]
Look at that output. Every sub-expression in the assertion is annotated with its actual value. You can see that name was “Groovy”, its length() returned 6, and the comparison to 5 returned false. With a traditional assert, you’d just get “Assertion failed” and have to add print statements to figure out what went wrong. Power assert gives you the full picture immediately.
12 Practical Assert Examples
Example 1: Basic Assert Statements
What we’re doing: Using assert for simple value checks – equality, comparisons, and null checks.
Example 1: Basic Assert
// Equality
def x = 10
assert x == 10
println "x == 10: passed"
// Comparison
assert x > 5
assert x <= 10
assert x != 0
println "Comparisons: passed"
// Null check
def name = "Groovy"
assert name != null
assert name
println "Not null: passed"
// Boolean
def active = true
assert active
println "Boolean: passed"
// Arithmetic
assert 2 + 2 == 4
assert 10 / 2 == 5
assert 7 % 3 == 1
println "Arithmetic: passed"
// String
assert "Hello".startsWith("He")
assert "Groovy".contains("oo")
assert "test" == "test"
println "String checks: passed"
println "\nAll basic assertions passed!"
Output
x == 10: passed Comparisons: passed Not null: passed Boolean: passed Arithmetic: passed String checks: passed All basic assertions passed!
What happened here: When assertions pass, they produce no output at all – execution continues silently. This is by design. You only hear from assert when something is wrong. Notice that assert name without any comparison uses Groovy truth – a non-null, non-empty string is truthy. This makes null checks clean and readable.
Example 2: Assert with Custom Messages
What we’re doing: Adding custom error messages to assertions using the colon syntax for better diagnostics.
Example 2: Custom Assert Messages
// Custom message syntax: assert expression : "message"
def age = 25
assert age >= 0 : "Age cannot be negative"
assert age <= 150 : "Age ${age} is unrealistic"
println "Age validation: passed"
// The message appears in the error
try {
def score = -5
assert score >= 0 : "Score must be non-negative, got: ${score}"
} catch (AssertionError e) {
println "Caught: ${e.message}"
}
println ""
// Messages can be any expression
try {
def user = [name: "Alice", role: "guest"]
assert user.role == "admin" : "User '${user.name}' has role '${user.role}', expected 'admin'"
} catch (AssertionError e) {
println "Caught: ${e.message}"
}
println ""
// Dynamic messages are helpful for debugging
def validateInput(String input, int minLength) {
assert input != null : "Input cannot be null"
assert input.length() >= minLength : "Input '${input}' is too short (${input.length()} < ${minLength})"
return true
}
println "Valid input: ${validateInput('Hello World', 5)}"
try {
validateInput("Hi", 5)
} catch (AssertionError e) {
println "Caught: ${e.message}"
}
Output
Age validation: passed Caught: Score must be non-negative, got: -5. Expression: (score >= 0). Values: score = -5 Caught: User 'Alice' has role 'guest', expected 'admin'. Expression: (user.role == admin) Valid input: true Caught: Input 'Hi' is too short (2 < 5). Expression: (input.length() >= minLength). Values: minLength = 5
What happened here: The colon syntax assert expression : "message" adds your custom message to the error output. The message can be any expression, including GString interpolation, which makes it easy to include the actual values that caused the failure. Notice how the power assert output still appears after your custom message – you get both your context and Groovy’s detailed diagnostics.
Example 3: Groovy Truth in Assert
What we’re doing: Understanding how Groovy’s truthiness rules apply to assert statements.
Example 3: Groovy Truth
// Non-empty string is truthy
assert "hello"
println "Non-empty string: truthy"
// Non-zero number is truthy
assert 42
assert -1
assert 0.1
println "Non-zero numbers: truthy"
// Non-empty collection is truthy
assert [1, 2, 3]
assert [key: "value"]
println "Non-empty collections: truthy"
// Non-null object is truthy
assert new Object()
println "Non-null object: truthy"
// Now here is what's falsy
def falsyValues = [
[value: null, name: "null"],
[value: "", name: "empty string"],
[value: 0, name: "zero"],
[value: [], name: "empty list"],
[value: [:], name: "empty map"],
[value: false, name: "false"]
]
falsyValues.each { item ->
try {
assert item.value
println "${item.name}: truthy (unexpected!)"
} catch (AssertionError e) {
println "${item.name}: falsy (assert failed as expected)"
}
}
println ""
// Practical use: null-safe checking
def config = [database: [host: "localhost", port: 5432]]
assert config.database : "Database config is missing"
assert config.database.host : "Database host is not configured"
println "Config validation: passed"
Output
Non-empty string: truthy Non-zero numbers: truthy Non-empty collections: truthy Non-null object: truthy null: falsy (assert failed as expected) empty string: falsy (assert failed as expected) zero: falsy (assert failed as expected) empty list: falsy (assert failed as expected) empty map: falsy (assert failed as expected) false: falsy (assert failed as expected) Config validation: passed
What happened here: Groovy’s truth rules make assert incredibly expressive. Instead of writing assert list != null && list.size() > 0, you just write assert list. The rules are simple: null, empty strings, empty collections, zero, and false are falsy. Everything else is truthy. This makes configuration validation and null checking very clean.
Example 4: Power Assert Diagnostics
What we’re doing: Exploring how power assert breaks down complex expressions to show exactly where the failure occurred.
Example 4: Power Assert Details
// Method chain diagnosis
try {
def text = "Hello Groovy World"
assert text.toLowerCase().split(" ").length == 2
} catch (AssertionError e) {
println "Chain diagnosis:"
println e.message
println ""
}
// Map access diagnosis
try {
def person = [name: "Alice", age: 30, city: "Seattle"]
assert person.age > 25 && person.city == "Portland"
} catch (AssertionError e) {
println "Map diagnosis:"
println e.message
println ""
}
// Arithmetic diagnosis
try {
def a = 10
def b = 3
assert a * b - 5 == 20
} catch (AssertionError e) {
println "Arithmetic diagnosis:"
println e.message
println ""
}
// Collection diagnosis
try {
def numbers = [10, 20, 30, 40, 50]
assert numbers.findAll { it > 25 }.size() == 4
} catch (AssertionError e) {
println "Collection diagnosis:"
println e.message
}
Output
Chain diagnosis:
assert text.toLowerCase().split(" ").length == 2
| | | | |
| | | 3 false
| | ['hello', 'groovy', 'world']
| 'hello groovy world'
'Hello Groovy World'
Map diagnosis:
assert person.age > 25 && person.city == "Portland"
| | | | | | |
|
What happened here: This is the real beauty of power assert. Look at the arithmetic example – you can see that a is 10, b is 3, their product is 30, minus 5 gives 25, which is not equal to 20. Every intermediate step is visible. The collection example shows that findAll returned 3 elements, not 4. You never need to add print statements to debug a failed assertion.
Example 5: Assert with Collections
What we’re doing: Using assert to validate lists, maps, and sets with common collection operations.
Example 5: Collection Assertions
// List assertions
def fruits = ["apple", "banana", "cherry", "date"]
assert fruits.size() == 4
assert fruits.contains("banana")
assert "cherry" in fruits
assert fruits[0] == "apple"
assert fruits[-1] == "date"
println "List assertions: passed"
// Sorted assertion
assert fruits.sort() == ["apple", "banana", "cherry", "date"]
println "Sort assertion: passed"
// Map assertions
def config = [host: "localhost", port: 8080, debug: true]
assert config.size() == 3
assert config.containsKey("host")
assert config.host == "localhost"
assert config.port instanceof Integer
println "Map assertions: passed"
// Set assertions
def set = [1, 2, 3, 2, 1] as Set
assert set.size() == 3
assert set == [1, 2, 3] as Set
println "Set assertions: passed"
// Collection transformations
def numbers = [1, 2, 3, 4, 5]
assert numbers.sum() == 15
assert numbers.max() == 5
assert numbers.min() == 1
assert numbers.collect { it * 2 } == [2, 4, 6, 8, 10]
assert numbers.findAll { it % 2 == 0 } == [2, 4]
assert numbers.every { it > 0 }
assert numbers.any { it > 4 }
println "Transformation assertions: passed"
println "\nAll collection assertions passed!"
Output
List assertions: passed Sort assertion: passed Map assertions: passed Set assertions: passed Transformation assertions: passed All collection assertions passed!
What happened here: Assert works beautifully with Groovy collections. The in operator checks containment, instanceof verifies types, and you can assert on the results of collection operations like collect, findAll, every, and any. When any of these fail, power assert will show you exactly what the collection contained and what the operation returned.
Example 6: Assert with Type Checking
What we’re doing: Using assert to verify variable types, class instances, and type coercions.
Example 6: Type Assertions
// Basic type checking
def text = "Hello"
assert text instanceof String
assert text.class == String
println "String type: passed"
// Number types
def intVal = 42
def longVal = 42L
def doubleVal = 3.14
def bigDecimal = 3.14G
assert intVal instanceof Integer
assert longVal instanceof Long
assert doubleVal instanceof BigDecimal // Groovy uses BigDecimal by default!
assert bigDecimal instanceof BigDecimal
println "Number types: passed"
// Collection types
assert [1, 2, 3] instanceof List
assert [1, 2, 3] instanceof ArrayList
assert [a: 1] instanceof Map
assert [a: 1] instanceof LinkedHashMap
assert (1..5) instanceof Range
println "Collection types: passed"
// Class hierarchy
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
def dog = new Dog()
assert dog instanceof Dog
assert dog instanceof Animal
assert !(dog instanceof Cat)
println "Class hierarchy: passed"
// Groovy coercion checks
def list = [1, 2, 3]
def array = list as int[]
assert array instanceof int[]
assert array.length == 3
println "Coercion: passed"
println "\nAll type assertions passed!"
Output
String type: passed Number types: passed Collection types: passed Class hierarchy: passed Coercion: passed All type assertions passed!
What happened here: Type assertions catch a surprisingly common class of bugs. Notice the Groovy gotcha – 3.14 is a BigDecimal in Groovy, not a double like in Java. Without an assert, you might not discover this until a method call fails because it expected a different type. The instanceof check also respects inheritance – a Dog is also an Animal.
Example 7: Assert with Closures and Methods
What we’re doing: Using assert to verify method return values and closure behavior.
Example 7: Method and Closure Assertions
// Assert method return values
def factorial(int n) {
assert n >= 0 : "Factorial requires non-negative input, got: ${n}"
if (n <= 1) return 1
return n * factorial(n - 1)
}
assert factorial(0) == 1
assert factorial(1) == 1
assert factorial(5) == 120
assert factorial(10) == 3628800
println "Factorial: passed"
// Assert with closures
def validator = { String input ->
assert input : "Input cannot be null or empty"
assert input.length() >= 3 : "Input '${input}' must be at least 3 characters"
assert input ==~ /[a-zA-Z]+/ : "Input '${input}' must contain only letters"
return true
}
assert validator("Hello")
assert validator("Groovy")
println "Closure validation: passed"
// Pre-condition and post-condition pattern
def divide(int a, int b) {
// Pre-conditions
assert b != 0 : "Cannot divide by zero"
def result = a / b
// Post-condition
assert result * b == a : "Division result verification failed"
return result
}
assert divide(10, 2) == 5
assert divide(100, 4) == 25
assert divide(9, 3) == 3
println "Division with contracts: passed"
// Assert in collect/transform
def parseNumbers(String csv) {
def numbers = csv.split(",").collect { it.trim() }
assert numbers.every { it ==~ /\d+/ } : "All values must be numeric: ${numbers}"
return numbers.collect { it.toInteger() }
}
def result = parseNumbers("1, 2, 3, 4, 5")
assert result == [1, 2, 3, 4, 5]
println "Parse numbers: passed"
println "\nAll method/closure assertions passed!"
Output
Factorial: passed Closure validation: passed Division with contracts: passed Parse numbers: passed All method/closure assertions passed!
What happened here: This shows the “design by contract” pattern – use assert for pre-conditions (validate inputs) and post-conditions (verify outputs). The factorial function asserts its input is non-negative. The divide function checks both that the divisor isn’t zero (pre-condition) and that the result is correct (post-condition). This pattern catches bugs right where they originate, not downstream.
Example 8: Assert with Exception Handling
What we’re doing: Combining assert with try-catch to verify that code throws expected exceptions.
Example 8: Exception Assertions
// Assert that an exception is thrown
def assertThrows(Class exceptionType, Closure code) {
try {
code()
assert false : "Expected ${exceptionType.simpleName} but no exception was thrown"
} catch (Exception e) {
assert exceptionType.isInstance(e) : "Expected ${exceptionType.simpleName} but got ${e.class.simpleName}: ${e.message}"
return e
}
}
// Test: division by zero
def ex1 = assertThrows(ArithmeticException) { 1 / 0 }
assert ex1.message.contains("by zero")
println "Division by zero: caught ${ex1.class.simpleName}"
// Test: index out of bounds
def ex2 = assertThrows(IndexOutOfBoundsException) { [1, 2, 3].get(10) }
println "Index bounds: caught ${ex2.class.simpleName}"
// Test: number format
def ex3 = assertThrows(NumberFormatException) { "not_a_number".toInteger() }
println "Number format: caught ${ex3.class.simpleName}"
// Test: null pointer
def ex4 = assertThrows(NullPointerException) {
String s = null
s.length()
}
println "Null pointer: caught ${ex4.class.simpleName}"
println ""
// Assert no exception is thrown
def assertNoException(Closure code) {
try {
def result = code()
assert true // Explicitly pass
return result
} catch (Exception e) {
assert false : "Unexpected exception: ${e.class.simpleName}: ${e.message}"
}
}
def result = assertNoException { [1, 2, 3].collect { it * 2 } }
assert result == [2, 4, 6]
println "No exception: passed"
println "\nAll exception assertions passed!"
Output
Division by zero: caught ArithmeticException Index bounds: caught IndexOutOfBoundsException Number format: caught NumberFormatException Null pointer: caught NullPointerException No exception: passed All exception assertions passed!
What happened here: The assertThrows helper function is a common pattern – it runs a closure and asserts that a specific exception type is thrown. If no exception occurs, or the wrong type is thrown, the assert fails with a descriptive message. This pattern is what testing frameworks like Spock build on internally. The assert false trick is useful for marking code paths that should never be reached.
Example 9: Assert with Regex Patterns
What we’re doing: Combining assert with regular expressions for string validation – a powerful combination.
Example 9: Assert with Regex
// Email validation
def validateEmail(String email) {
assert email : "Email cannot be null or empty"
assert email ==~ /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/ : "Invalid email format: ${email}"
return true
}
assert validateEmail("user@example.com")
assert validateEmail("admin@technoscripts.com")
println "Email validation: passed"
// Date format validation
def validateDate(String date) {
assert date ==~ /\d{4}-\d{2}-\d{2}/ : "Date must be in YYYY-MM-DD format, got: ${date}"
def (year, month, day) = date.split("-").collect { it.toInteger() }
assert year >= 1900 && year <= 2100 : "Year ${year} out of range"
assert month >= 1 && month <= 12 : "Month ${month} out of range"
assert day >= 1 && day <= 31 : "Day ${day} out of range"
return true
}
assert validateDate("2026-03-08")
assert validateDate("2025-12-25")
println "Date validation: passed"
// Version string validation
def validateVersion(String version) {
assert version ==~ /\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?/ : "Invalid version: ${version}"
def matcher = version =~ /(\d+)\.(\d+)\.(\d+)/
def (_, major, minor, patch) = matcher[0]
assert major.toInteger() >= 0 : "Major version cannot be negative"
return [major: major.toInteger(), minor: minor.toInteger(), patch: patch.toInteger()]
}
def v = validateVersion("5.0.3")
assert v.major == 5
assert v.minor == 0
assert v.patch == 3
println "Version validation: passed (${v})"
// URL validation
def urls = ["https://groovy-lang.org", "http://localhost:8080/path", "ftp://files.example.com"]
urls.each { url ->
assert url ==~ /^(https?|ftp):\/\/[^\s]+$/ : "Invalid URL: ${url}"
}
println "URL validation: passed"
println "\nAll regex assertions passed!"
Output
Email validation: passed Date validation: passed Version validation: passed ([major:5, minor:0, patch:3]) URL validation: passed All regex assertions passed!
What happened here: Assert and regex together create a concise validation toolkit. The ==~ match operator returns a boolean, which is exactly what assert needs. The date validator goes beyond format checking – it also validates the range of year, month, and day values. The version validator shows how you can extract and return structured data after validation passes. For more on regex in Groovy, see our regular expressions guide.
Example 10: Assert for Data Structure Validation
What we’re doing: Validating complex nested data structures using assert – a common need when processing JSON or configuration data.
Example 10: Data Structure Validation
// Validate a user object
def validateUser(Map user) {
assert user : "User cannot be null or empty"
assert user.name : "User name is required"
assert user.name instanceof String : "User name must be a string"
assert user.age instanceof Integer : "User age must be an integer"
assert user.age > 0 && user.age < 150 : "User age ${user.age} is invalid"
assert user.email ==~ /\S+@\S+\.\S+/ : "Invalid email: ${user.email}"
return true
}
def user = [name: "Alice", age: 30, email: "alice@example.com"]
assert validateUser(user)
println "User validation: passed"
// Validate nested config
def validateConfig(Map config) {
assert config.database : "Database config is required"
assert config.database.host : "Database host is required"
assert config.database.port instanceof Integer : "Database port must be an integer"
assert config.database.port > 0 && config.database.port <= 65535 : "Port out of range: ${config.database.port}"
if (config.cache) {
assert config.cache.ttl instanceof Integer : "Cache TTL must be an integer"
assert config.cache.ttl > 0 : "Cache TTL must be positive"
}
return true
}
def config = [
database: [host: "localhost", port: 5432, name: "mydb"],
cache: [ttl: 3600, maxSize: 1000]
]
assert validateConfig(config)
println "Config validation: passed"
// Validate a list of records
def records = [
[id: 1, status: "active", score: 85],
[id: 2, status: "active", score: 92],
[id: 3, status: "inactive", score: 78]
]
assert records.size() > 0 : "Records list cannot be empty"
assert records.every { it.id instanceof Integer } : "All IDs must be integers"
assert records.every { it.status in ["active", "inactive"] } : "Invalid status found"
assert records.every { it.score >= 0 && it.score <= 100 } : "Scores must be 0-100"
def activeCount = records.count { it.status == "active" }
assert activeCount >= 1 : "At least one record must be active"
println "Records validation: passed (${activeCount} active out of ${records.size()})"
println "\nAll data structure assertions passed!"
Output
User validation: passed Config validation: passed Records validation: passed (2 active out of 3) All data structure assertions passed!
What happened here: This pattern is invaluable when working with JSON APIs, configuration files, or database results. Instead of writing defensive code with null checks scattered everywhere, you put all your validations up front with assert. If any check fails, you get a clear message about exactly what’s wrong. The nested config validator shows how to handle optional sections – only validate the cache config if it exists.
Example 11: Assert in Groovy Scripts and Pipelines
What we’re doing: Using assert as a lightweight testing framework for scripts, automation, and CI/CD pipelines.
Example 11: Script-Level Assert
// Self-testing utility class
class StringUtils {
static String reverse(String s) {
assert s != null : "Cannot reverse null string"
return s.reverse()
}
static String capitalize(String s) {
assert s : "Cannot capitalize null or empty string"
return s[0].toUpperCase() + s[1..-1].toLowerCase()
}
static boolean isPalindrome(String s) {
assert s != null : "Cannot check palindrome of null"
def cleaned = s.toLowerCase().replaceAll(/[^a-z0-9]/, '')
return cleaned == cleaned.reverse()
}
// Self-test method
static void runTests() {
// reverse tests
assert reverse("hello") == "olleh"
assert reverse("") == ""
assert reverse("a") == "a"
println " reverse: 3 tests passed"
// capitalize tests
assert capitalize("hello") == "Hello"
assert capitalize("WORLD") == "World"
assert capitalize("groovy") == "Groovy"
println " capitalize: 3 tests passed"
// isPalindrome tests
assert isPalindrome("racecar")
assert isPalindrome("A man a plan a canal Panama")
assert !isPalindrome("hello")
assert isPalindrome("12321")
println " isPalindrome: 4 tests passed"
}
}
println "Running StringUtils tests:"
StringUtils.runTests()
println ""
// Environment validation script
def validateEnvironment() {
def groovyVersion = GroovySystem.version
assert groovyVersion : "Could not determine Groovy version"
println "Groovy version: ${groovyVersion}"
def javaVersion = System.getProperty("java.version")
assert javaVersion : "Could not determine Java version"
println "Java version: ${javaVersion}"
def osName = System.getProperty("os.name")
assert osName : "Could not determine OS"
println "OS: ${osName}"
return true
}
println "Environment check:"
assert validateEnvironment()
println "\nAll script assertions passed!"
Output
Running StringUtils tests: reverse: 3 tests passed capitalize: 3 tests passed isPalindrome: 4 tests passed Environment check: Groovy version: 4.0.24 Java version: 17.0.11 OS: Windows 10 All script assertions passed!
What happened here: Assert is a legitimate lightweight testing approach for scripts and utility classes. The StringUtils.runTests() pattern gives you a self-testing class – run the tests and if no AssertionError is thrown, everything works. This is simpler than setting up a full testing framework when you just need quick validation. The environment validation pattern is useful at the start of deployment scripts to verify preconditions.
Example 12: Advanced Power Assert Patterns
What we’re doing: Combining assert with advanced Groovy features for expressive validation code.
Example 12: Advanced Assert Patterns
// Assert with with() for scoped validation
def server = [host: "localhost", port: 8080, protocol: "https", maxConnections: 100]
server.with {
assert host == "localhost"
assert port > 0 && port <= 65535
assert protocol in ["http", "https"]
assert maxConnections >= 10
}
println "Server config (with): passed"
// Assert with tap() for inline validation
def processedList = [3, 1, 4, 1, 5, 9, 2, 6]
.sort()
.tap { assert it[0] == 1 : "First element should be 1" }
.tap { assert it[-1] == 9 : "Last element should be 9" }
.unique()
.tap { assert it.size() == 7 : "Expected 7 unique elements" }
println "Tap validation: passed (${processedList})"
// Multiple return value assertion
def divmod(int a, int b) {
assert b != 0 : "Cannot divide by zero"
return [quotient: a.intdiv(b), remainder: a % b]
}
def result = divmod(17, 5)
assert result.quotient == 3
assert result.remainder == 2
assert result.quotient * 5 + result.remainder == 17
println "Divmod: passed (${result})"
// Assert with spread operator
def people = [
[name: "Alice", age: 30],
[name: "Bob", age: 25],
[name: "Charlie", age: 35]
]
assert people*.name == ["Alice", "Bob", "Charlie"]
assert people*.age.every { it >= 18 }
assert people*.age.sum() == 90
println "Spread assertions: passed"
// Builder pattern with assertions
class QueryBuilder {
String table
List<String> columns = []
String whereClause
QueryBuilder from(String t) { assert t : "Table required"; table = t; this }
QueryBuilder select(String... cols) { assert cols : "Columns required"; columns.addAll(cols); this }
QueryBuilder where(String w) { whereClause = w; this }
String build() {
assert table : "Table not set"
assert columns : "No columns selected"
def sql = "SELECT ${columns.join(', ')} FROM ${table}"
if (whereClause) sql += " WHERE ${whereClause}"
return sql
}
}
def sql = new QueryBuilder()
.from("users")
.select("name", "email", "age")
.where("age > 18")
.build()
assert sql == "SELECT name, email, age FROM users WHERE age > 18"
println "Query builder: passed"
println "SQL: ${sql}"
println "\nAll advanced assertions passed!"
Output
Server config (with): passed Tap validation: passed ([1, 2, 3, 4, 5, 6, 9]) Divmod: passed ([quotient:3, remainder:2]) Spread assertions: passed Query builder: passed SQL: SELECT name, email, age FROM users WHERE age > 18 All advanced assertions passed!
What happened here: The with() method scopes the assertions to the server map, making the code clean and readable. The tap() method is brilliant for inline validation in a method chain – it lets you assert intermediate states without breaking the chain. The spread operator *. collects a property from every element, making batch assertions concise. The builder pattern shows how assert creates fail-fast APIs – if you forget to call from() or select(), you get a clear error.
Assert vs Exceptions vs Testing Frameworks
When should you use assert versus throwing exceptions or using a testing framework? Here’s a practical comparison:
| Approach | Use When | Error Type | Can Be Disabled |
|---|---|---|---|
assert | Checking developer assumptions, pre/post conditions, scripts | PowerAssertionError | No (in Groovy) |
| Exceptions | Validating user input, recoverable errors, API contracts | Custom exceptions | No |
| Spock/JUnit | Unit and integration testing | Test failures | Not applicable |
When to Use Which
// ASSERT - for internal invariants and development checks
def processOrder(Map order) {
assert order.items : "Bug: processOrder called with no items"
assert order.total >= 0 : "Bug: negative order total"
// ... process the order
}
// EXCEPTION - for input validation and API boundaries
def createUser(String name, String email) {
if (!name?.trim()) {
throw new IllegalArgumentException("Name is required")
}
if (!email?.contains("@")) {
throw new IllegalArgumentException("Invalid email: ${email}")
}
// ... create the user
}
// SPOCK/JUNIT - for automated test suites
// class OrderSpec extends Specification {
// def "order total should be sum of item prices"() {
// given: def order = new Order()
// when: order.addItem("Widget", 9.99)
// then: order.total == 9.99
// }
// }
println "Pattern examples compiled successfully"
Output
Pattern examples compiled successfully
Rule of thumb: Use assert when a failure means there’s a bug in your code. Use exceptions when a failure means the user or caller provided bad input. Use a testing framework when you need organized, repeatable test suites with reports.
Common Assert Patterns
Here are assert patterns you’ll use repeatedly:
Common Assert Patterns
// 1. Guard clause pattern
def processItems(List items) {
assert items : "Items list required"
assert items.size() <= 1000 : "Too many items: ${items.size()}"
return items.collect { it.toString().toUpperCase() }
}
assert processItems(["a", "b"]) == ["A", "B"]
println "Guard clause: passed"
// 2. Range validation
def setVolume(int level) {
assert level in 0..100 : "Volume must be 0-100, got: ${level}"
return level
}
assert setVolume(50) == 50
println "Range validation: passed"
// 3. State machine assertion
def validTransitions = [
"NEW": ["PROCESSING"],
"PROCESSING": ["COMPLETED", "FAILED"],
"COMPLETED": [],
"FAILED": ["PROCESSING"]
]
def transition(String from, String to) {
assert from in validTransitions.keySet() : "Unknown state: ${from}"
assert to in validTransitions[from] : "Invalid transition: ${from} -> ${to}"
return to
}
assert transition("NEW", "PROCESSING") == "PROCESSING"
assert transition("PROCESSING", "COMPLETED") == "COMPLETED"
println "State machine: passed"
// 4. Idempotency check
def sortList(List items) {
def sorted = items.sort(false) // sort without modifying original
assert sorted == sorted.sort(false) : "Sort is not stable"
return sorted
}
assert sortList([3, 1, 4, 1, 5]) == [1, 1, 3, 4, 5]
println "Idempotency: passed"
println "\nAll patterns passed!"
Output
Guard clause: passed Range validation: passed State machine: passed Idempotency: passed All patterns passed!
Performance and Production Considerations
Since Groovy’s assert is always enabled, there are a few things to keep in mind for production code:
- Assert expressions are always evaluated – don’t put expensive operations inside an assert unless you need them
- Power assert has overhead – Groovy’s AST transformation captures all sub-expression values, which adds some overhead compared to a simple
ifcheck - Use for invariants, not control flow – assert should catch bugs, not handle expected conditions
- Custom messages with GString are lazy-ish – the message expression is only fully constructed if the assert fails, so
assert x > 0 : "expensive ${compute()}"won’t callcompute()on success - For hot paths – if you have assertions in tight loops processing millions of items, consider using simple
ifchecks with thrown exceptions instead
Best Practices
DO:
- Use assert for pre-conditions at method entry points to validate assumptions about inputs
- Add custom messages with context – include the actual values that caused the failure
- Use assert in scripts and automation for quick validation without a testing framework
- use Groovy truth –
assert listis cleaner thanassert list != null && list.size() > 0 - Use assert for post-conditions to verify that your code produces correct results
- Keep assertions as simple expressions so power assert output is readable
DON’T:
- Use assert for user input validation – throw exceptions instead, since assert means “there’s a bug”
- Put side effects inside assert expressions – the expression should only read values, not modify state
- Assume assert can be disabled in Groovy – unlike Java, Groovy assertions are always active
- Use assert as a replacement for proper error handling – catch expected failures with try-catch
- Write overly complex assert expressions – break them into multiple simpler assertions for clearer diagnostics
Conclusion
We covered the key techniques for Groovy assert and power assert – from basic assertions and custom messages to collection validation, type checking, exception testing, and design-by-contract patterns. Power assert is genuinely one of Groovy’s best features – the visual breakdown of failed expressions makes debugging immediate and obvious.
The bottom line: assert is your coding safety net. Use it to codify your assumptions about how your code should behave. When those assumptions break, power assert will tell you exactly what went wrong, with every intermediate value visible. It’s like having print statements pre-installed at every point in your expression.
For related topics, check out our Groovy Regular Expressions guide for combining assert with regex validation.
Summary
- Groovy’s
assertis always enabled – no-eaflag needed - Power assert shows the value of every sub-expression when an assertion fails
- Use
assert expression : "message"for custom error messages with context - Assert uses Groovy truth –
null, empty strings, empty collections, and0are falsy - Use assert for developer assumptions and invariants, not for user input validation
If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.
Up next: Groovy Enum – Constants, Methods, and Best Practices
Frequently Asked Questions
Can Groovy assert be disabled like Java’s assert?
No. Unlike Java’s assert which requires the -ea JVM flag to be enabled, Groovy’s assert is always active. It cannot be disabled at runtime or compile time. This makes it reliable for validation – you can count on assertions always being checked.
What is the difference between Groovy assert and power assert?
They are the same thing. In Groovy, every assert statement is automatically a power assert. When an assertion fails, Groovy’s AST transformation captures and displays the value of every sub-expression in the assertion, giving you a detailed visual breakdown of what went wrong.
Should I use assert or throw exceptions for input validation?
Use exceptions for input validation and assert for internal invariants. Assert means ‘there is a bug in my code if this fails.’ Exceptions mean ‘the caller provided invalid input.’ For user-facing validation, throw IllegalArgumentException or a custom exception. For developer assumptions, use assert.
What does Groovy assert throw when it fails?
Groovy assert throws a PowerAssertionError, which extends java.lang.AssertionError. The error message includes the power assert output showing the value of every sub-expression, plus any custom message you provided with the colon syntax.
Does Groovy assert have a performance impact?
Yes, there is some overhead. Groovy’s AST transformation captures all sub-expression values even when the assertion passes, which adds some cost compared to a simple if check. For most code this is negligible, but in tight loops processing millions of items, consider using plain if-throw patterns instead.
Related Posts
Previous in Series: Groovy Regular Expressions – Pattern Matching
Next in Series: Groovy Enum – Constants, Methods, and Best Practices
Related Topics You Might Like:
- Groovy Regular Expressions – Pattern Matching
- Groovy Enum – Constants, Methods, and Best Practices
- Groovy def Keyword – Dynamic Typing Explained
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment