Choosing Between @CompileStatic and @CompileDynamic in Groovy for Performance‑Critical Components
Learn when to apply Groovy's @CompileStatic for speed and when to keep @CompileDynamic for metaprogramming, with a compact comparison table and a concrete code example.
01 Apr 2026, 17:27 UTC

Decision: When to use @CompileStatic vs @CompileDynamic
If you are building a Groovy component that runs in a tight loop or processes large data sets, you must decide whether to sacrifice some dynamic language features for the speed of static compilation. The useful takeaway is: apply @CompileStatic to performance‑critical code paths and keep @CompileDynamic (or no annotation) only where you need metaprogramming, DSL syntax, or runtime method addition.
Constraints and goals
- Execution speed matters (e.g., >10⁶ iterations).
- Code must remain readable and maintainable.
- Only a subset of Groovy’s dynamic features is required.
Comparison of options
| Aspect | @CompileStatic | @CompileDynamic |
|---|---|---|
| Compilation model | Static type checking, Java‑like bytecode | Dynamic call site (InvokeDynamic) fallback |
| Performance | Typically 2‑5× faster for numeric loops | Similar to plain Groovy dynamic dispatch |
| Supported features | No method missing, no runtime property addition, no spread operator on collections unless typed | Full Groovy metaprogramming (methodMissing, propertyMissing, spread, DSL builders) |
| Error detection | Missing method or type mismatch caught at compile time | Errors surface only at runtime |
Trade‑offs
- Using @CompileStatic removes the ability to add methods via
metaClassor rely onmethodMissing. If your DSL depends on those, keep the method dynamic. - Annotating every method with @CompileStatic can make the code look verbose; reserve it for hot spots.
- Mixing static and dynamic calls inside the same tight loop can cause a transition overhead between call sites; try to keep the boundary outside the loop.
Concrete implementation example
The following Groovy class shows a static‑compiled factorial method and a dynamic DSL builder for a simple configuration DSL.
package com.example
import groovy.transform.CompileStatic
import groovy.transform.CompileDynamic
@CompileStatic
class MathService {
static long factorial(int n) {
long result = 1L
for (int i = 2; i <= n; i++) {
result *= i
}
return result
}
}
@CompileDynamic
class ConfigBuilder {
def config = [:]
def methodMissing(String name, args) {
config[name] = args.size() == 1 ? args[0] : args
return this
}
Map getResult() { config }
}
Validation steps
- Save the class to
src/main/groovy/com/example/MathService.groovy(or any source folder). - Compile with the Groovy compiler:
groovyc -d build/classes src/main/groovy/com/example/MathService.groovy. You need read access to the source folder and write access to the output directory. - Inspect the generated bytecode for the static method:
javap -cp build/classes -v com.example.MathService | grep -A20 'factorial'. Look for the absence ofinvokedynamicorCallSiteinstructions. - For the dynamic builder, repeat the
javapcheck oncom.example.ConfigBuilderand verify thatinvokedynamicappears in themethodMissingimplementation. - Optionally, run a simple benchmark (e.g., using
System.nanoTime) to compare the execution time ofMathService.factorial(20)called 1 000 000 times with the static version versus a version without @CompileStatic. Do not treat the measured numbers as absolute; they only illustrate the relative difference on your machine.
Limitations and practical checks
- @CompileStatic will fail to compile if you attempt to call a method that is not resolvable at compile time (e.g.,
someObj.unknownMethod()). Use this as a compile‑time safety net. - If you need to add methods at runtime via
metaClass, keep the target class or method dynamic; otherwise you will get aMissingMethodExceptionat runtime. - To verify that the static compilation actually improved speed in your application, run a realistic workload under a profiler (e.g., Java Flight Recorder) and compare the elapsed time with and without the annotation on the same code path.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.