Best Practices for Production Groovy
Prerequisites: Static compilation, Testing with Spock
Groovy lets you write code that's either beautifully concise or impossible to maintain. These habits keep it the former.
Type your boundaries
Use def freely inside short methods and scripts, but give public
methods, fields, and constructor parameters real types. Types document
the API, enable IDE completion, and let @TypeChecked or
@CompileStatic catch mistakes.
Be static by default in application code
Put @CompileStatic on classes (or enable it project-wide with a
compiler config script) and opt out with @CompileDynamic for the parts
that need dynamic features — DSLs, metaprogramming, JSON navigation.
Keep metaprogramming contained
- Prefer traits, categories, and extension modules to global metaclass changes.
- Keep
methodMissinglogic small and throwMissingMethodExceptionfor anything you don't handle, so typos still fail loudly.
Handle nulls and errors deliberately
- Use
?.and?:at the edges (input, JSON, config), not as a reflex everywhere — anullthat silently propagates is hard to debug. - Catch specific exceptions, and never swallow them with an empty
catch. - Validate input early and fail with clear messages.
Security
- Never pass untrusted text to
GroovyShell,Eval, orevaluate. - In
Jenkinsfiles, use single-quotedshstrings for secrets. - Use parameterized queries (
groovy.sql.Sqlwith GString parameters or?placeholders is safe; building SQL with+is not). - Keep Groovy and dependencies updated; pin
@Grabversions.
A small, well-behaved class
import groovy.transform.CompileStatic
import groovy.transform.Immutable
@Immutable
class LineItem {
String sku
int quantity
BigDecimal unitPrice
BigDecimal getTotal() { unitPrice * quantity }
}
@CompileStatic
class Invoice {
private final List<LineItem> items = []
Invoice add(String sku, int quantity, BigDecimal unitPrice) {
if (!sku) throw new IllegalArgumentException('sku is required')
if (quantity <= 0) throw new IllegalArgumentException("quantity must be positive: $quantity")
items << new LineItem(sku, quantity, unitPrice)
this
}
BigDecimal getTotal() {
items.sum(BigDecimal.ZERO) { LineItem it -> it.total } as BigDecimal
}
List<String> lines() {
items.collect { LineItem it -> "${it.sku.padRight(6)} ${it.quantity} x ${it.unitPrice}".toString() }
}
}
def invoice = new Invoice()
.add('PEN', 3, 1.20)
.add('BOOK', 1, 12.50)
invoice.lines().each { println it }
println "total: $invoice.total"
try {
invoice.add('INK', 0, 5)
} catch (IllegalArgumentException e) {
println "rejected: $e.message"
}
PEN 3 x 1.20
BOOK 1 x 12.50
total: 16.10
rejected: quantity must be positive: 0
Immutable values, a statically compiled service with typed methods,
validation with clear messages, BigDecimal for money, and chainable
calls — idiomatic Groovy that's also easy to maintain.
Checklist
- Tests (Spock) for business logic; CodeNarc in the build.
- Consistent style: the Groovy style guide, formatted by your IDE.
- Small methods, closures under a few lines, no clever one-liners that need a comment to explain.
- Know your host's Groovy version (Gradle, Jenkins, Grails) before using new features.
What's next
Try the Advanced Projects, then read What You Should Know Now — and What's Next for where to go from here.