Reading — step 1 of 4
Learn
Groovy is dynamic by default. Every method call goes through reflection-like dispatch. Modern features let you opt into static dispatch for performance.
Default: dynamic
def double(x) {
x * 2
}
double(5) // works for int
double("hi") // works for String — "hihi" via String * 2
The runtime checks x.multiply(2) (String) or x * 2 (numeric). Slow but flexible.
@CompileStatic
import groovy.transform.CompileStatic
@CompileStatic
def sum(List<Integer> nums) {
int total = 0
for (int n : nums) {
total += n
}
return total
}
- Method calls resolved at compile time
- Type errors caught early
- Closer to Java performance
metaClassmods don't apply
Benchmarks: dynamic Groovy is often 2-5x slower than static Groovy. For hot loops, the difference is measurable.
When to use which
Static (@CompileStatic):
- Performance-critical code
- Library code where types matter
- Refactoring safety (compile errors catch breakage)
Dynamic (default):
- DSLs (Gradle, Spock — they NEED dynamic)
- Configuration code
- Glue scripts
- Where flexibility outweighs speed
@TypeChecked
Checks types at compile time but keeps dynamic dispatch:
import groovy.transform.TypeChecked
@TypeChecked
def greet(String name) {
return "Hello, $name"
}
// greet(42) // compile error: Integer != String
Compromise — type safety without losing metaprogramming.
Static type checking caveats
@CompileStatic
def ambiguous(map) {
map.size() // ERROR — what is map's type?
}
Without type annotation, the compiler can't infer. Use:
defworks (becomesObject)- Explicit types:
Map map,List<Integer> nums - Generic params:
def <T> processList(List<T> items)
Performance comparison
Dynamic Groovy: 100 ns/op
@TypeChecked: 80 ns/op (slight win — early type checks)
@CompileStatic: 30 ns/op (3x faster)
Pure Java: 25 ns/op (5% faster than @CompileStatic)
Numbers vary; measure your code.
Mixing static and dynamic
You can apply @CompileStatic per-class, per-method, or globally:
// Class-level — applies to all methods
@CompileStatic
class Calculator { ... }
// Method-level
class Calculator {
@CompileStatic
int fastAdd(int a, int b) { a + b }
def slowAdd(a, b) { a + b } // dynamic
}
// File-level (with @CompileStatic on a top-level class)
Indy (invokedynamic) backend
Groovy 2.1+ has an invokedynamic mode that dramatically speeds up dynamic calls (60-80% of static performance). Enable with the -indy JAR. Most modern Groovy uses indy by default.
When the dynamism matters
- Spock: relies on
methodMissing, AST transformations - Gradle: every block's delegate magic
- HTTP/JSON builders:
json.user { ... }—useris intercepted viamethodMissing - Mocking libraries: Mock(Class) generates dynamic stubs
These DON'T work with @CompileStatic. Keep that code dynamic; static-compile your business logic.
The verdict
- New Groovy code: prefer
@CompileStaticunless you NEED dynamic - DSLs and config: keep dynamic
- Performance: profile first; static usually wins
- Maintenance: static catches more bugs at compile time
Groovy started as fully dynamic — its evolution toward optional static is a recognition that dynamic wasn't always the right answer.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…