Optional Typing in Groovy
Groovy lets you declare variables with def (dynamically typed, resolved at runtime) or with an explicit static type like String name = "Ada" (statically declared, checked at compile time for assignment compatibility), and both styles can be mixed freely in the same class or even the same method, which is why Groovy is described as optionally typed rather than purely dynamic or purely static.
Cricket analogy: Declaring def player = 'Kohli' is like a scorer's notebook column left generic enough to later hold a bowler's economy rate instead of a batter's name, whereas a column explicitly headed Runs, a typed field, is checked to only ever contain run totals.
def vs Explicit Types
A variable declared with def can be reassigned to any type at runtime - def x = 5; x = 'now a string' is perfectly legal - because def really means no declared type constraint, infer as Object, whereas int x = 5; x = 'text' fails at compile time because the declared type constrains what can be assigned.
Cricket analogy: def x = 5; x = 'now a string' succeeding is like a scoreboard digit display that can be repurposed mid-match to show a player's name instead of a number, whereas a purpose-built Runs LED display, typed int, would reject non-numeric input outright.
def x = 5 // dynamically typed
x = 'now a string' // legal - def has no type constraint
int y = 5
// y = 'text' // fails to compile: cannot assign String to int
@groovy.transform.CompileStatic
class PriceCalculator {
BigDecimal total(List<BigDecimal> prices) {
prices.sum() as BigDecimal
}
}
Static Type Checking with @TypeChecked and @CompileStatic
Because ordinary Groovy code runs through the dynamic Groovy MOP (meta-object protocol) even when variables have declared types, adding @groovy.transform.TypeChecked to a class or method turns on compile-time type checking that catches type errors the dynamic runtime would otherwise defer to execution time, and @groovy.transform.CompileStatic goes further by bypassing the dynamic dispatch machinery entirely and compiling method calls directly to bytecode, closing the gap with plain Java-level performance.
Cricket analogy: Adding @CompileStatic to a scoring class is like switching from a casual scorer jotting notes freehand to the official DRS system that checks every entry against strict rules before it's accepted, catching mistakes before the over even ends rather than after the match.
@CompileStatic doesn't just add checks - it changes runtime behavior. Because it compiles method calls statically, dynamic meta-programming hooks like methodMissing, propertyMissing, and runtime-added methods via the ExpandoMetaClass no longer work inside a @CompileStatic scope. If your class relies on Groovy's dynamic MOP for its API, apply @CompileStatic selectively (per method, using @CompileDynamic to opt individual methods back out) rather than blanket-annotating the whole class.
Trade-offs of Dynamic vs Static Code
Dynamic (def-heavy) Groovy code is faster to write and more flexible for scripting, DSLs, and metaprogramming-heavy code, but defers many errors, typos in method names, wrong argument types, to runtime; statically typed or @CompileStatic code trades some of that flexibility for compile-time safety, IDE autocompletion accuracy, and noticeably better runtime performance since it skips dynamic method dispatch, which is why production services and performance-sensitive library code in Groovy often lean on @CompileStatic while build scripts and glue code stay fully dynamic.
Cricket analogy: Choosing fully dynamic Groovy for a quick analysis script is like a commentator freestyling stats live on air, fast and flexible but occasionally wrong, whereas @CompileStatic production code is like the official third-umpire system, slower to set up but far more reliable under scrutiny.
- def declares a variable with no type constraint (inferred as Object), allowing reassignment to any type at runtime.
- An explicitly typed variable (e.g. int x) is checked for assignment compatibility, unlike def.
- Ordinary Groovy code runs through the dynamic Groovy MOP even when variables have declared types.
- @groovy.transform.TypeChecked enables compile-time type checking that catches errors before runtime.
- @groovy.transform.CompileStatic bypasses dynamic dispatch entirely, compiling directly to Java-like bytecode for near-Java performance.
- @CompileStatic disables dynamic meta-programming hooks like methodMissing/propertyMissing within its scope.
- Dynamic Groovy favors flexibility and rapid scripting; static/CompileStatic Groovy favors compile-time safety and performance.
Practice what you learned
1. What does declaring a variable with def mean in Groovy?
2. Which annotation makes Groovy compile method calls statically, bypassing the dynamic meta-object protocol for better performance?
3. What does @TypeChecked add to a Groovy class or method, compared to plain dynamic Groovy?
4. What is a side effect of applying @CompileStatic to a class?
5. Which style of Groovy code is generally preferred for performance-sensitive production services?
Was this page helpful?
You May Also Like
Closures in Groovy
How Groovy's first-class Closure type captures its defining scope, and how owner, delegate, and resolveStrategy power Groovy's DSL syntax.
String Interpolation (GStrings)
How Groovy's GString type interpolates expressions into double-quoted strings, how it differs from plain String, and the map-key pitfall to avoid.
Operators and Truth in Groovy
Groovy's safe navigation, Elvis, and spaceship operators, operator overloading, and the rules of Groovy Truth for evaluating objects in boolean contexts.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics