When to Use Groovy’s @CompileStatic: A Practical Guide
Use Groovy’s @CompileStatic to get Java‑style type safety and lower runtime overhead. This guide shows how it works, how to verify it, its limits, and common pitfalls with concrete examples.
22 Mar 2026, 23:08 UTC

Why Use @CompileStatic?
Groovy normally resolves method and property calls at runtime via its meta‑object protocol. @CompileStatic tells the compiler to perform Java‑style type checking and emit direct bytecode. The result is twofold: 1) compile‑time type safety, and 2) removal of the dynamic dispatch overhead that can affect CPU‑bound code such as tight loops or numeric calculations.
How the Compiler Works
When you annotate a class, method, or script with @CompileStatic, the Groovy compiler checks that every method call and property access is resolvable at compile time. If a call cannot be resolved, a compile‑time error is emitted. The generated bytecode contains ordinary invokevirtual or invokestatic instructions instead of the meta‑class calls that power Groovy’s dynamic features.
Example: a small utility class compiled statically.
import groovy.transform.CompileStatic
@CompileStatic
class MathUtils {
static int add(int a, int b) { a + b }
static int square(int x) { x * x }
}
// Usage in another class
class Demo {
static void main(String[] args) {
println MathUtils.add(3, 4) // prints 7
println MathUtils.square(5) // prints 25
}
}
Verifying Static Compilation
- Save the file as
src/MathUtils.groovyandDemo.groovy. - Compile with the Groovy compiler:
groovyc -cp . -d out src/MathUtils.groovy src/Demo.groovy - Inspect the generated bytecode for the
MathUtilsclass:javap -c out/MathUtils.classYou should see direct arithmetic instructions such as
iaddandimuland no calls togroovy.lang.MetaClass. - Run the demo to confirm behaviour:
java -cp out Demo
Limits of Static Compilation
- Dynamic method resolution –
methodMissingandpropertyMissingare not invoked. - Runtime meta‑class changes –
ExpandoMetaClassmodifications after compilation have no effect. - GString evaluation – GStrings are compiled to plain
Stringconcatenation; lazy interpolation is not preserved. - Closure delegate strategies –
delegateandresolveStrategyare ignored; the closure behaves as if its owner is the delegate. - DSLs that rely on missing methods – Builders that use
methodMissingto create tags or configuration nodes will break when annotated.
Common Pitfalls
Annotating an Entire Script
Applying @CompileStatic to a script that uses a DSL (e.g., MarkupBuilder) causes MissingMethodException because the compiler cannot resolve the dynamic tag methods at compile time. Keep the DSL section unannotated or annotate only the computational parts.
Mixing @CompileStatic with @Delegate
When a delegate’s type is determined at runtime or via a map, the generated forwarding methods may call methods that do not exist, leading to runtime errors. Ensure the delegate type is a concrete class known at compile time, or move the delegation logic to a non‑static method.
Forgetting @CompileDynamic
If you need a specific method to remain dynamic inside a statically compiled class, annotate that method with @CompileDynamic. Without it, the compiler will reject any dynamic call inside the method.
Practical Checklist
- Annotate only the parts of your code that perform pure computations or data transformations.
- Verify that no dynamic features are used in those sections.
- Compile with
groovycand inspect bytecode withjavap -cto confirm static dispatch. - Run a small benchmark (e.g., a loop of 107 additions) and compare
System.nanoTime()before and after to observe any performance difference. - Use
@CompileDynamicfor methods that must retain Groovy’s dynamic behaviour.
By following this approach you gain compile‑time safety and potentially faster execution while keeping the flexibility to use Groovy’s dynamic features where they matter.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.