+1
Gianluca Sartori -- https://dueuno.com On Mon, 1 Jun 2026 at 19:15, Per Nyfelt <[email protected]> wrote: > Hi, > > I think it would be nice if we supported caching for Groovy scripts to > speed up execution time for subsequent runs. Below is a GEP style proposal > for that. What do you think? > GEP: Persistent Script Compilation Cache for the Groovy Command-Line > Launcher Metadata > > *Type:* Feature > *Status:* Draft > *Target Groovy Version:* TBD > *Author:* Per Nyfelt > *Discussion:* TBD > *Created:* 2026-06-01 > Abstract > > This proposal introduces an optional persistent compilation cache for > Groovy scripts executed through the groovy command-line launcher. > > When a script is executed, the launcher may store the generated class > files in a local cache. On later executions, if the script source and > relevant compilation inputs are unchanged, the launcher may load the cached > class files instead of parsing, transforming, and compiling the script > again. > > The goal is to improve startup time for short-lived Groovy scripts and > command-line tools while preserving current semantics by default. > Motivation > > Groovy is frequently used for scripting, automation, build tooling, data > processing, and command-line utilities. In these use cases, process > lifetime is often short, and startup overhead matters. > > For simple scripts, actual execution time may be negligible compared to: > > - > > JVM startup > - > > loading the Groovy runtime > - > > parsing the script > - > > applying AST transformations > - > > bytecode generation > - > > class loading > > Recent and ongoing JVM work, such as CDS and Project Leyden, can reduce > JVM startup and class-loading overhead. However, raw Groovy script > execution still pays the cost of compiling the script source on every > invocation. > > Groovy already caches compiled script classes during the lifetime of a > GroovyClassLoader, but this cache is lost when the process exits. A > persistent launcher-level cache would allow repeated invocations of > unchanged scripts to skip most compilation work. > > This would make Groovy more attractive for command-line scripting and > developer tooling, especially compared with languages and runtimes that > already persist bytecode or compilation artifacts between runs. > Goals > > The goals of this proposal are: > > 1. > > Reduce repeated startup overhead for unchanged Groovy scripts. > 2. > > Avoid changing Groovy language semantics. > 3. > > Make the feature safe and conservative by default. > 4. > > Provide explicit ways to disable, clear, and inspect the cache. > 5. > > Ensure cache invalidation accounts for relevant compilation inputs. > 6. > > Allow future integration with JVM startup technologies such as CDS or > Leyden-style caches. > > Non-Goals > > This proposal does not aim to: > > 1. > > Replace groovyc. > 2. > > Change Groovy compilation semantics. > 3. > > Cache arbitrary runtime results. > 4. > > Guarantee improved performance for all scripts. > 5. > > Provide a distributed or shared build cache. > 6. > > Cache scripts run inside long-lived embedded Groovy runtimes unless > those runtimes explicitly opt in. > 7. > > Solve dependency resolution caching for @Grab, although it must > interact safely with it. > > Proposed Behavior > > When running: > > groovy myscript.groovy > > the launcher may: > > 1. > > Compute a cache key from the script and compilation environment. > 2. > > Look for previously generated class files matching that key. > 3. > > If found and valid, load the cached classes. > 4. > > Otherwise compile the script normally and store the generated classes > in the cache. > > The user-visible behavior of the script must remain the same as if the > script had been compiled from source during that invocation. > Cache Location > > The default cache location should follow platform conventions. > > Suggested defaults: > > Linux/Unix: > $XDG_CACHE_HOME/groovy/script-cache > or ~/.cache/groovy/script-cache > > macOS: > ~/Library/Caches/Groovy/script-cache > > Windows: > %LOCALAPPDATA%\Groovy\script-cache > > A system property or environment variable should allow overriding the > location: > > groovy -Dgroovy.script.cache.dir=/path/to/cache myscript.groovy > > Possible environment variable: > > GROOVY_SCRIPT_CACHE_DIR=/path/to/cache > > Enabling and Disabling > > The cache should initially be opt-in unless the Groovy project decides the > invalidation model is sufficiently conservative for default use. > > Possible command-line options: > > groovy --script-cache myscript.groovy > groovy --no-script-cache myscript.groovy > groovy --clear-script-cache > > Possible system properties: > > -Dgroovy.script.cache=true > -Dgroovy.script.cache=false > -Dgroovy.script.cache.dir=/path/to/cache > > If the feature later proves safe and reliable, it could become enabled by > default for normal file-based scripts. > Cache Key > > The cache key must include enough information to avoid reusing stale or > incompatible bytecode. > > At minimum, the key should include: > > - > > absolute or canonical script path > - > > script source hash > - > > Groovy version > - > > Java version or class file target version > - > > effective classpath > - > > compiler configuration > - > > invokedynamic setting > - > > preview/incubating compiler flags where relevant > - > > script base class > - > > active AST transformations > - > > relevant system properties that affect compilation > > For scripts using @Grab, the resolved dependency coordinates and artifact > versions should be included after dependency resolution. > > A conservative implementation may choose to skip caching when the > compilation environment cannot be reliably fingerprinted. > Cache Contents > > The cache should store: > > - > > generated .class files > - > > metadata describing the compilation environment > - > > cache format version > - > > source hash > - > > Groovy version > - > > Java/classfile target > - > > classpath fingerprint > - > > timestamp of creation > - > > optional diagnostic information > > The cache format should be treated as internal and may change between > Groovy versions. > Invalidation > > A cached script must be invalidated when any relevant compilation input > changes. > > Examples: > > - > > script source changed > - > > Groovy version changed > - > > Java target changed > - > > classpath changed > - > > AST transform implementation changed > - > > compiler configuration changed > - > > @Grab dependencies changed > - > > cache format changed > > If validation fails or metadata is unreadable, the launcher should > silently fall back to normal compilation unless diagnostics are enabled. > Diagnostics > > The launcher should provide optional diagnostics. > > Examples: > > groovy --script-cache-info myscript.groovy > groovy --script-cache-verbose myscript.groovy > > Possible output: > > Groovy script cache: miss > Reason: source hash changed > > or: > > Groovy script cache: hit > Cache entry: ~/.cache/groovy/script-cache/... > > Diagnostics should be disabled by default to preserve normal script output. > Security Considerations > > The cache stores executable bytecode. Therefore: > > 1. > > Cache entries should be private to the current user by default. > 2. > > The cache directory should not be world-writable. > 3. > > The launcher should avoid loading cache entries with unsafe > permissions. > 4. > > Cache keys should prevent cross-user or cross-project collisions. > 5. > > The cache should not weaken existing script security assumptions. > > On systems where permissions cannot be verified reliably, the launcher may > disable caching or use a more conservative mode. > Concurrency > > Multiple processes may execute the same script concurrently. > > The implementation should use atomic writes, temporary files, and safe > renames to avoid corrupted cache entries. > > If a cache entry is being written by another process, the launcher may > either wait briefly, ignore the incomplete entry, or compile normally. > Interaction with Existing Groovy Facilities GroovyClassLoader > > The existing in-memory class cache remains useful within a single JVM > process. The proposed persistent cache complements it by surviving across > process invocations. > groovyc > > This proposal does not replace groovyc. Users who want explicit > ahead-of-time compilation can continue using groovyc. > > The script cache is intended for the common case where users execute > source scripts directly with the groovy command. > @Grab > > Scripts using @Grab may be cached only if the resolved dependency set can > be included in the cache key. > > A first implementation may conservatively disable persistent script > caching for scripts using @Grab. > AST Transformations > > AST transformations affect generated bytecode and must be part of the > compilation fingerprint. If this cannot be done reliably, caching should be > disabled for affected scripts. > Possible Implementation Approach > > One possible implementation is: > > 1. > > Extend the groovy launcher with a cache-aware script runner. > 2. > > Before compilation, compute a ScriptCacheKey. > 3. > > Look for a matching cache entry. > 4. > > If present, load generated classes using an appropriate class loader. > 5. > > If absent, compile the script as today. > 6. > > Capture generated bytecode. > 7. > > Persist generated bytecode and metadata atomically. > 8. > > On future runs, validate metadata before loading. > > The implementation should be internal and not expose cache internals as > stable public API in the first version. > Testing > > Tests should cover: > > - > > cache miss on first execution > - > > cache hit on second execution > - > > invalidation when script source changes > - > > invalidation when classpath changes > - > > invalidation when Groovy version or cache format changes > - > > disabling the cache > - > > clearing the cache > - > > concurrent execution > - > > scripts with imports > - > > scripts with local classes > - > > scripts using AST transformations > - > > scripts using different compiler configurations > - > > failure fallback to normal compilation > - > > cache directory permission checks where supported > > Performance tests should measure: > > - > > trivial script > - > > script with imports > - > > script with AST transforms > - > > script with larger source file > - > > script with dependency-heavy classpath > - > > script using @CompileStatic > - > > script using dynamic Groovy features > > Backward Compatibility > > This proposal should be backward compatible. > > If disabled, behavior is unchanged. > > If enabled, the observable behavior of a script should be equivalent to > normal source compilation. If the cache cannot guarantee this, it should > not be used. > Risks > > The main risks are: > > 1. > > Incorrect cache invalidation. > 2. > > Security issues from loading cached bytecode. > 3. > > Increased launcher complexity. > 4. > > Hard-to-debug behavior if cached bytecode differs from source > compilation. > 5. > > Limited benefit for scripts where runtime dominates startup. > > These risks can be mitigated by making the feature initially opt-in, using > conservative invalidation, providing diagnostics, and falling back to > normal compilation whenever uncertainty exists. > Alternatives Considered Use groovyc > > Users can already precompile scripts with groovyc. However, this changes > the workflow and removes the convenience of directly running .groovy > source files. > Rely Only on JVM Startup Improvements > > JVM-level startup improvements help Groovy, but they do not remove > Groovy-specific parsing, AST transformation, and bytecode generation costs. > Keep Only In-Memory Caching > > Groovy already benefits from in-memory class caching in long-lived > processes. This does not help repeated short-lived invocations of the > groovy command. > External Wrapper Tool > > An external script runner could implement persistent caching, but > launcher-level support would be more discoverable, portable, and consistent. > Future Work > > Future extensions could include: > > - > > enabling the cache by default > - > > cache statistics > - > > integration with CDS or Leyden-style JVM caches > - > > shared cache support for trusted environments > - > > Gradle/Maven integration > - > > reusable public APIs for embedders > - > > support for caching generated stubs where applicable > - > > smarter dependency fingerprinting for @Grab > > Conclusion > > A persistent script compilation cache would address a long-standing pain > point for Groovy as a scripting language: repeated startup cost for > short-lived scripts. > > By caching generated class files across invocations, the groovy launcher > could avoid unnecessary repeated parsing, AST transformation, and bytecode > generation when scripts are unchanged. > > Implemented conservatively, this feature would preserve Groovy semantics > while making Groovy scripts feel significantly faster in day-to-day > command-line use. > > >
