Using @CompileStatic in Groovy for Faster, Safer Code
Groovy’s dynamic flexibility can hide bugs and slow down performance‑critical code. Applying @CompileStatic forces static compilation, catching errors early and boosting speed.
28 Jan 2026, 04:16 UTC

Problem: Dynamic Groovy Can Hide Errors and Slow Down Hot Paths
Groovy’s dynamic dispatch lets you write concise scripts, but it also means that type mismatches or missing methods are only discovered at runtime. In performance‑critical sections—such as algorithms executed thousands of times per request—the overhead of dynamic method lookup can add measurable latency.
How @CompileStatic Works
The @CompileStatic annotation instructs the Groovy compiler to treat the annotated class or method as if it were written in Java. Dynamic features like methodMissing, propertyMissing, and runtime metaprogramming are disabled. Instead, the compiler generates direct Java bytecode, which allows static type checking at compile time and eliminates the dispatch overhead at runtime.
Worked Example: Fibonacci Service
Dynamic Version
// src/FibonacciDynamic.groovy
class FibonacciDynamic {
def fib(int n) {
if (n < 2) return n
return fib(n - 1) + fib(n - 2)
}
}
Static Version with @CompileStatic
// src/FibonacciStatic.groovy
import groovy.transform.CompileStatic
@CompileStatic
class FibonacciStatic {
int fib(int n) {
if (n < 2) return n
return fib(n - 1) + fib(n - 2)
}
}
To compile both versions, open a terminal where the Groovy SDK is on your PATH and run:
groovyc -d out src/FibonacciDynamic.groovy
javac -d out src/FibonacciStatic.groovy // the static version is already Java‑compatible
Create a simple benchmark class (place it in src/ and compile similarly):
// src/FibonacciBenchmark.java
public class FibonacciBenchmark {
public static void main(String[] args) {
int runs = 500_000;
long start, time;
// dynamic
FibonacciDynamic dyn = new FibonacciDynamic();
start = System.nanoTime();
for (int i = 0; i < runs; i++) dyn.fib(20);
time = System.nanoTime() - start;
System.out.println("Dynamic time: " + time + " ns");
// static
FibonacciStatic stat = new FibonacciStatic();
start = System.nanoTime();
for (int i = 0; i < runs; i++) stat.fib(20);
time = System.nanoTime() - start;
System.out.println("Static time: " + time + " ns");
}
}
Run the benchmark:
java -cp out FibonacciBenchmark
You should see the static version consistently faster, while both produce the same Fibonacci numbers. Compile‑time errors (for example, calling fib("5")) will be reported by the compiler only for the static version.
Trade‑off: Lost Dynamic Features
Because @CompileStatic removes dynamic dispatch, any reliance on Groovy’s metaprogramming facilities—such as ExpandoMetaClass, categories, or methodMissing—will cause a compilation error when the annotation is present. If a class needs those features, either keep it dynamic or refactor the dynamic parts into a separate class that does not carry the annotation.
Actionable Closing
- Profile your application to find hot spots where Groovy methods are invoked frequently.
- Add
@CompileStaticto those classes or methods one at a time. - Run your existing unit or Spock test suite to confirm behavior is unchanged.
- Optionally, repeat the simple benchmark above to observe the performance gain.
By applying the annotation incrementally, you gain static safety and speed where it matters most, while preserving Groovy’s flexibility elsewhere.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.