Choosing Groovy's @CompileStatic for Static Type Checking and Performance
A decision guide that compares Groovy's dynamic mode with @CompileStatic, outlines trade‑offs, and shows a runnable example with verification steps.
19 Sept 2025, 05:47 UTC

Decision and constraints
When writing Groovy code you can keep the default dynamic behaviour or enable static type checking with the @CompileStatic transformation. Use @CompileStatic when:
- the algorithm does not rely on runtime metaprogramming (e.g.,
methodMissing,propertyMissing,ExpandoMetaClass), - you want compile‑time safety for variable types and method signatures,
- execution speed matters for tight loops or computationally heavy sections.
If any of those dynamic features are required, stay with the default mode or isolate the dynamic parts in separate methods or classes.
Supported options
| Option | Static checking | Performance | Dynamic features supported |
|---|---|---|---|
| @CompileStatic | Yes | Near‑Java speed (dispatch eliminated) | No – metaprogramming, dynamic GString evaluation, method/property missing |
| Default (dynamic) | No | Slower due to meta‑object dispatch | Full Groovy dynamism |
Trade‑offs
Enabling @CompileStatic gives you:
- Early detection of type mismatches at compile time.
- Reduced runtime overhead because calls are resolved to direct JVM invocations.
The costs are:
- Loss of runtime flexibility – any attempt to use
methodMissing,propertyMissing,ExpandoMetaClass, or a GString whose value is resolved at runtime will cause a compilation error. - Some Groovy‑only DSLs that rely on metaprogramming may fail when compiled statically; you must either avoid them or keep those parts dynamic.
Concrete implementation
The following script defines a recursive Fibonacci method, annotates it with @CompileStatic, measures execution time, and prints the result. The same code without the annotation can be used for a direct performance comparison.
import groovy.transform.CompileStatic
@CompileStatic
int fib(int n) {
if (n < 2) return n
return fib(n - 1) + fib(n - 2)
}
def start = System.nanoTime()
println fib(30)
def elapsed = System.nanoTime() - start
println "Time: ${elapsed / 1e6} ms"
Run the script with Groovy 3.0 or later:
- Save the code to a file, e.g.,
Fib.groovy. - Compile:
groovyc -d out Fib.groovy(requires write permission to theoutdirectory). - Execute:
groovy -cp out Fib.
The output shows the Fibonacci value (832040) and the elapsed time in milliseconds. To see the effect of static compilation, repeat the steps after removing or commenting out the @CompileStatic line; the dynamic version will typically take noticeably longer for the same computation.
Verification and limitations
Verification steps:
- Confirm that the script compiles without errors when
@CompileStaticis present. - Run the script and record the printed time.
- Remove the annotation, recompile, and run again; compare the two times.
- Introduce a dynamic feature inside the annotated method, e.g.,
println "${java.util.Date.now()}", and observe a compile‑time error confirming the restriction.
Limitations to keep in mind:
- Any library or DSL that expects runtime metaprogramming may throw
MissingMethodExceptionor fail to compile when used inside a@CompileStaticblock. - The performance gain is most visible in CPU‑bound loops; I/O‑bound code may see little difference.
- Debugging stack traces can look slightly different because the compiler generates synthetic methods.
By following the decision criteria, comparing the options in the table, and validating with the example above, you can determine whether @CompileStatic suits your Groovy project.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.