Skip to content
AgileLogics
6 min read

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 methodMissing logic small and throw MissingMethodException for 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 — a null that 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, or evaluate.
  • In Jenkinsfiles, use single-quoted sh strings for secrets.
  • Use parameterized queries (groovy.sql.Sql with GString parameters or ? placeholders is safe; building SQL with + is not).
  • Keep Groovy and dependencies updated; pin @Grab versions.

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.