Groovy standards (reference: August 2026)
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Groovy today is above all a configuration language for other tools, not an application language. Almost
all the Groovy written in 2026 is a Jenkinsfile, a build.gradle or a Spock specification.
Writing a backend in Groovy is a decision that has to be justified against Java or Kotlin, and
it almost never survives the justification (§7). This skill is structured by use case.
Triggers: .groovy, .gvy, Jenkinsfile, vars/*.groovy, src/**/*.groovy of a Shared Library,
build.gradle, settings.gradle, gradle.properties, @NonCPS, @CompileStatic, @DelegatesTo,
codenarc, Spock specs in src/test/groovy.
Not applicable:
jvm-spring-standards (critical boundary): the JVM, the JDK, the GC, memory tuning, Spring
and the application model are theirs. The Groovy language and the Gradle and Jenkins DSLs are
ours. If the question is "which JDK and with which flags", it is theirs; if it is "how do I write this
build.gradle", it is ours.
cicd-standards (critical boundary, in both directions): pipeline strategy, the
gates, artifact signing, the SBOM, OIDC, runner security and isolation are theirs.
How the Jenkinsfile is written as Groovy code — declarative vs scripted, CPS, @NonCPS,
Shared Libraries, the Script Security sandbox — is ours. A "the pipeline does not
pass the gate" problem is theirs; a "the pipeline does not serialise and fails at step 3" one is ours.
scala-standards and clojure-standards (the other JVM languages), iac-standards,
container-runtime-security-standards (the isolation of the container where the agent runs),
lua-standards and perl-standards (nothing in common), appsec-standards,
vulnerability-management-standards, secrets-management-standards, sql-standards,
observability-standards.
2. Default decisions
Verify the latest version on the web before pinning it in a real project (§8).
Apache Groovy is alive and actively maintained — a relevant fact because the opposite is often
assumed. It is an Apache Software Foundation project (Apache-2.0) and as of Aug 2026 it maintains three
parallel lines, with releases from the same week:
| Line |
Verified status (Aug 2026) |
| Groovy 5.0.x |
Current stable. 5.0.8 (2026-07-29) |
| Groovy 4.0.x |
Maintained. 4.0.33 (2026-07-29) |
| Groovy 6.0.0 |
BETA-1 (2026-08-01). Do not use in production |
Groovy 5 JDK requirements (official release notes): JDK17+ to build Groovy and
JDK 11 is the minimum supported JRE version; tested on JDK 11–25. In practice: you do not choose the
Groovy version if you work inside Gradle or Jenkins — the tool imposes it,
because it embeds its own Groovy. Check which one before using a language feature.
| Piece |
Choice |
Verified |
Licence (raw LICENSE) |
| Language |
Apache Groovy 5.0.x |
5.0.8 |
Apache-2.0 |
| Build |
Gradle 9.x |
9.6.1 (2026-06-26); 9.7 in RC |
Apache-2.0 |
| CI |
Jenkins LTS |
2.568.x line; weekly 2.575 |
MIT |
| Tests |
Spock |
2.4 (2025-12-11), stable after a long series of milestones |
Apache-2.0 |
| Lint |
CodeNarc |
see §8 (not verified) |
see §8 |
Gradle: build.gradle versus build.gradle.kts
The real criteria today, unambiguously:
- New project → Kotlin DSL (
.kts). It is what Gradle generates by default. Verbatim from the release
notes of version 8.2: "Kotlin DSL is now the default option when generating a new project with the
init task." An engineering reason, not a fashion one: static typing, real autocompletion and navigation
in the IDE, and compile-time errors in the build itself. The Groovy DSL is dynamic and its
"API" is metaprogramming and delegation: the IDE guesses.
- The Groovy DSL is NOT deprecated and remains supported. There is no end-of-life pressure.
- Existing
build.gradle → do not migrate it for the sake of it. Migrating a large build is a
project with real risk (plugins, ext, closures with implicit delegation, ad-hoc logic in
loose .gradle files) and its benefit is developer experience, not functionality. Criteria: migrate if you are already
going to touch the build in depth, if the build is complex enough that the lack of types
costs real time, or if you are going to move it to buildSrc/convention plugins (where the Kotlin DSL gains
much more). Otherwise, it stays and is kept well written.
- Whichever the DSL: build logic outside the script.
buildSrc/ or an included build with
convention plugins; build.gradle(.kts) must be declarative. Version catalog
(gradle/libs.versions.toml) as the single source of versions. Gradle Wrapper committed, with
gradle-wrapper.properties pointing to a distribution with distributionSha256Sum — a wrapper
without a checksum is execution of downloaded code without verification.
3. Jenkins: the Jenkinsfile is not normal Groovy
This is the section that prevents the most incidents. A Jenkinsfile looks like Groovy and does not behave like
Groovy.
- CPS transformation: Jenkins compiles the pipeline with the Groovy parser but does not execute its
bytecode: a
CompilationCustomizer rewrites almost every operation into
continuation-passing style, and its own interpreter executes them. Reason: the complete program state
is serialised to disk (program.dat in the build directory) on every asynchronous
operation, so that the build survives a Jenkins restart and continues where it was.
- Consequences that must be internalised:
- Every variable live at the point of a step call must be serialisable. A regex
Matcher,
an InputStream, a File → NotSerializableException at an apparently
random point. Rule: non-serialisable objects live and die inside a @NonCPS, or are
discarded (m = null) before the next step.
- It is slow, and the official documentation is explicit: it is not meant to be efficient and must
be limited to high-level glue code, with the real logic in external programs via
sh/bat.
A loop over 10,000 items inside a pipeline is a design error. There are also Groovy
constructs that the interpreter does not cover well (closures over collections, inheritance).
@NonCPS: marks a method so that it is compiled and executed with native Groovy semantics (except for the
sandbox checks). Hard rules:
- You cannot call CPS methods or pipeline steps from a
@NonCPS. Its only
correct use is pure computation: serialisable data in, serialisable summary out.
- Do not accept, return or store non-serialisable values from a
@NonCPS.
@Overrides of methods of binary classes (toString(), equals()) must be @NonCPS,
because the binary code will call them from a non-CPS context.
- Jenkins detects the most common error (calling CPS code from a non-CPS context) and warns about it in the
log; that warning is a failure, not noise.
- Declarative by default.
pipeline { agent … stages { … } } is the correct form for 95% of
cases: fixed structure, validatable, with native post, options, environment and matrix, and
much less loose Groovy to serialise. Scripted (node { … }) only for control flows
that declarative cannot express, and even then a bounded script { } block inside the
declarative is preferred over an entire scripted pipeline.
- Shared Libraries for everything repeated across repos:
vars/ (global steps, one file per
step, with call()) and src/ (normal Groovy classes, here you can indeed type and test). The library
is a repo versioned with tags, and pipelines reference it by tag or by commit, never
by a moving branch: @Library('mylib@v3.2.0'). A library referenced by master is a global
change without review that applies to all pipelines at once.
- It is tested: the
src/ classes with Spock, in their own build. A pipeline whose only test
environment is production is not code, it is a bet.
Jenkinsfile short by design: it orchestrates, it does not implement. Any logic longer than a few lines
goes down to a script in the repo (bash/python) or to the Shared Library. Double benefit: it can be
run and tested locally without Jenkins, and it does not go through CPS.
4. The language: what to use and what to avoid
@CompileStatic whenever possible; @TypeChecked at a minimum. Dynamic Groovy pays for every
call through runtime dispatch and does not detect a method typo until the line is executed — in
a pipeline, that is "fails at minute 40 of the build". Apply it at class level in all the logic
of a Shared Library (src/) and in any Groovy application code.
- Real limit and why it is not applied everywhere: the Gradle and Jenkins DSLs depend on
dynamic metaprogramming (delegation,
methodMissing, propertyMissing), so the pipeline script
and the build.gradle cannot be @CompileStatic. That is exactly the reason
why the logic must leave the script and go down to classes that can be.
- Closures and delegation are the mechanism of the DSLs:
delegate, owner, resolveStrategy.
When you write your own DSL, annotate @DelegatesTo on the closure parameter — without that, neither the IDE
nor @CompileStatic can resolve anything inside the block, and your DSL is untouchable.
- AST transforms:
@Immutable, @Canonical, @Builder, @Memoized, @Slf4j are useful and
legitimate. Writing your own AST transforms is vetoed except in extraordinary cases: they are code that
runs in the compiler, almost impossible to debug and that breaks with every Groovy version.
- GString and its interpolation trap — two classic incidents, both vetoed:
- In SQL:
sql.execute("SELECT * FROM t WHERE id = $id") with groovy.sql.Sql does
parameterise the GString (it is a deliberate special case), but
sql.execute("SELECT * FROM t WHERE id = ${id}".toString()) does not: converting to String
loses the parameterisation and you have injection. Any concatenation or .toString() on a
GString destined for SQL is vetoed. Use explicit placeholders and a parameter list.
- In logs:
log.debug("payload: $obj") always evaluates the interpolation, even if the DEBUG
level is off. A hot-path cost and, worse, leakage: in a pipeline that can dump a
credentials object into the build log. Use the logger's placeholder form, and never
interpolate credential objects or environment maps.
?. (safe navigation) and ?: (Elvis) are idiomatic and correct; deeply chained ?.
is not robustness, it is hiding a null that should not exist — fail early at the edge.
def versus types: with @CompileStatic, explicit types in public signatures always. def only
for obvious locals.
- Groovy truth:
0, "", [], [:] and null are false. It is not Java nor Lua; an if (x)
check on a number or a collection does not mean "it is not null".
5. Security: Groovy is arbitrary code execution on the JVM
Starting point without nuance: a Jenkinsfile or a build.gradle from an untrusted repository
executes whatever it wants inside your runner, with your runner's identity. It is not a configuration
file; it is a program. Everything that follows derives from that.
- Gradle:
./gradlew build on an unknown repo executes its build code before compiling
anything. A PR that modifies build.gradle, settings.gradle or buildSrc/ is a PR that modifies what
your CI executes. Consequence: workflows that run builds from forks never receive secrets
nor tokens with write permissions; Gradle plugins are pinned to an exact version from the
version catalog and resolved from an own repository or a mirror, never from an arbitrary
repository declared in the script itself; the wrapper carries distributionSha256Sum.
- Jenkins Script Security: the Groovy sandbox is what prevents a pipeline from taking control of the
controller. Its model is an allowlist of methods, with manual approval (In-process Script
Approval) for whatever falls outside. Two rules:
- FORBIDDEN to disable the sandbox. The Pipeline runtime has access to Jenkins
internals: retrieving encrypted credentials, triggering deployments, deleting artifacts. A pipeline without
a sandbox is full control of the controller for anyone who can write to the repo.
- Approving a method is a security decision, not an administrative unblock. Approving things
like
getClass, getDeclaredMethods, anything reflection-related or System.* amounts to
disabling the sandbox through the back door. If a pipeline needs something the sandbox does not
allow, the correct solution is to move it down to an approved Shared Library or to a plugin, not
to approve the method.
- There have been and will be sandbox bypass CVEs (implicit casts of the Groovy runtime,
default parameter expressions in CPS methods, rebuild of an unapproved script). Corollary:
the sandbox is containment, not a boundary. The boundary is who can write to the repo and what
the agent can reach.
- Controller and agent hygiene: nothing is compiled or executed on the controller — everything on
ephemeral agents. The Jenkins script console (
/script) is arbitrary execution as the
Jenkins user: restricted and audited access.
- Secrets: through credentials binding (
withCredentials), never in the Jenkinsfile, in
the repo's gradle.properties nor in global environment variables. set -x in a sh and a
GString interpolation are the two usual ways a secret ends up in the build log.
- Runner supply chain — the control that actually cuts. Precedent already established in this
catalogue and verified: in 2026 malicious packages published with cryptographically valid
SLSA Build L3 attestations were documented (the worm over 42
@tanstack packages in May 2026,
and the wave over @redhat-cloud-services in June), because the attackers hijacked the
legitimate pipeline and the provenance correctly described a compromised build. Signed
provenance is no longer enough: it says where the artifact came from, not that it is safe. What does cut
the vector, applied to the Groovy/CI world:
- Pin by SHA or by digest everything that enters the runner: GitHub Actions by commit SHA
(not by tag), agent images by digest, Gradle distribution by
distributionSha256Sum,
plugins and dependencies by exact version with checksum/signature verification
(gradle/verification-metadata.xml), Shared Libraries by immutable tag or commit.
- No dependency resolution from an arbitrary repository declared in the build script
itself.
- Ephemeral runner, without long-lived credentials, with bounded egress, and OIDC trust tied to a
specific workflow and protected branch, not to "the repository". The full strategy lives in
cicd-standards; what is ours is that the Groovy file is the vector.
6. Performance and operability
- In a pipeline the dominant cost is almost always the CPS interpreter: the lever is not optimising
Groovy, it is taking work out of the pipeline towards
sh/scripts on the agent.
- Gradle: build cache and configuration cache enabled;
--scan/Develocity to know where the
time goes before touching anything. Custom tasks with declared inputs/outputs (otherwise they are neither
incremental nor cacheable). No logic in the configuration phase.
- Jenkins:
timeout on every stage (a pipeline without a timeout is an agent blocked forever),
retry only on idempotent steps, disableConcurrentBuilds() where there is shared state, and
buildDiscarder — program.dat files and artifacts fill disks.
- Observability of CI as a service: duration per stage, failure and flakiness rate, time in
queue. A pipeline without metrics cannot be improved (telemetry, in
observability-standards).
- The same command must run locally and in CI. If a build only works on the agent, it is a build bug.
7. Sustainability and prohibitions
- Cadence: Gradle follows a fast cadence — update the wrapper regularly and read the
release notes first (Gradle major jumps do break plugins). Jenkins: use the LTS
line, with monthly updates and plugin updates as a recurring task, not annual: the
majority of Jenkins security advisories are about plugins.
- A Jenkins plugin with no releases and no maintainer is security debt: it is replaced or retired.
Audit the list of installed plugins once a quarter and delete what is not used (each
plugin is attack surface on the controller).
- Own deprecations in a Shared Library: versioned with tags and with a retirement window; the
pipelines pointing to an old tag are visible debt, which is exactly what is wanted.
List of prohibitions (veto):
- ❌ FORBIDDEN to disable the Jenkins Script Security sandbox.
- ❌ FORBIDDEN to approve reflection methods,
System.*, getClass/getDeclaredMethods or similar
in the script approval. If that is needed, the design is wrong.
- ❌ FORBIDDEN to run builds from forks or from untrusted repos with access to secrets or to
credentials with write permissions.
- ❌ Referencing a Shared Library, an action or an image by branch or moving tag. Immutable tag, commit
or digest.
- ❌ Gradle wrapper without
distributionSha256Sum; dependencies without checksum/signature verification.
- ❌ Non-trivial logic inside the
Jenkinsfile or the build.gradle: move it down to a Shared Library,
buildSrc/convention plugin, or a script executable on the agent.
- ❌ Loops over large collections or real computation inside a CPS pipeline.
- ❌ Calling pipeline steps from a
@NonCPS method; keeping non-serialisable objects alive
across a step.
- ❌ GString converted to
String in a SQL query; interpolation of credential objects or of the
environment in logs.
- ❌ Dynamic Groovy (without
@CompileStatic) in application code or in the src/ of a Shared Library.
- ❌ Own AST transforms except with extraordinary written justification.
- ❌ Migrating a large and healthy
build.gradle to .kts with no reason other than fashion.
- ❌ Running code on the Jenkins controller instead of on ephemeral agents.
- ❌ Pipeline without
timeout or buildDiscarder.
- ❌ Choosing Groovy for new application code. Groovy is the correct choice in three places:
a
build.gradle that already exists, a Jenkinsfile, and a Spock suite (which remains an
excellent test framework and a fully legitimate use, even for testing Java or Kotlin code).
Outside that, a new service on the JVM is written in Java or Kotlin — static typing, tooling,
performance, hiring and ecosystem all play against Groovy, and the project itself
positions itself as a scripting and DSL language, not as an application platform.
8. Mandatory web verification
Before pinning versions or APIs, verify online (WebSearch/WebFetch and the Atom feeds
https://github.com/OWNER/REPO/releases.atom; api.github.com returns 403 unauthenticated):
- Groovy: latest 5.0.x and the status of Groovy 6 (as of Aug 2026 at
6.0.0-BETA-1: not production),
and the JDK matrix of the version you are going to use at groovy-lang.org/releasenotes/. Verified as of
Aug 2026 for Groovy 5: JDK 11 minimum JRE, JDK 17+ to build Groovy, tested up to JDK 25.
- Which Groovy your tool embeds (Gradle and Jenkins ship their own, usually older than
the latest stable): it is what decides which language features you can use.
- Gradle: latest stable (9.6.1 as of Aug 2026, with 9.7 in RC) and the release notes before
bumping the wrapper. Confirm that the Groovy DSL is still supported and not deprecated.
- Jenkins: the number of the current LTS and, above all, the security advisories of Jenkins and
its plugins (
jenkins.io/security/advisories) — including the Groovy sandbox bypasses.
- Spock: latest stable (2.4 as of Aug 2026) and its declared compatibility with your Groovy
and JUnit Platform versions.
- CVEs of Groovy, Gradle and the Jenkins plugins in use (osv.dev / GitHub Advisories).
Declared gaps as of Aug 2026 (do not fill in from memory):
- CodeNarc: current version, licence and maintenance status not verified. Before pinning it
as the default linter in a project, check its latest release and its raw
LICENSE. If it is
stalled, the Groovy quality gate rests on @CompileStatic + compilation + the tests, which
is where the real value is anyway.
Declared discrepancy: the What's new in Gradle 9 page on gradle.org places the adoption of the
Kotlin DSL as the recommended option in "April 2023 (Gradle 8.2)", whereas the release notes of
version 8.2 on docs.gradle.org show no publication date confirming it and the real date
of 8.2 is not verified. What is verified verbatim in the 8.2 notes is the substantive
claim: "Kotlin DSL is now the default option when generating a new project with the init task."
Use the claim, not the date.
If the web contradicts this document, the web wins — flag the discrepancy.
1---2name: groovy-standards3description: Use when writing or reviewing Groovy code - .groovy and .gvy files, Jenkinsfile (declarative or scripted), Jenkins Shared Libraries with vars/ and src/, @NonCPS annotations and Script Security sandbox approvals, build.gradle and settings.gradle in the Groovy DSL versus build.gradle.kts, gradle init defaults, Spock specifications in src/test/groovy, @CompileStatic/@TypeChecked/@DelegatesTo, AST transforms, GString interpolation, or codenarc.4---56# Groovy standards (reference: August 2026)78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910## 1. Scope and triggers1112**Groovy today is above all a configuration language for other tools, not an application language.** Almost13all the Groovy written in 2026 is a `Jenkinsfile`, a `build.gradle` or a Spock specification.14Writing a backend in Groovy is a decision that has to be justified against Java or Kotlin, and15it almost never survives the justification (§7). This skill is structured by use case.1617Triggers: `.groovy`, `.gvy`, `Jenkinsfile`, `vars/*.groovy`, `src/**/*.groovy` of a Shared Library,18`build.gradle`, `settings.gradle`, `gradle.properties`, `@NonCPS`, `@CompileStatic`, `@DelegatesTo`,19`codenarc`, Spock specs in `src/test/groovy`.2021**Not applicable**:22- **`jvm-spring-standards` (critical boundary)**: **the JVM, the JDK, the GC, memory tuning, Spring23 and the application model are theirs**. The **Groovy language** and the **Gradle and Jenkins DSLs** are24 ours. If the question is "which JDK and with which flags", it is theirs; if it is "how do I write this25 `build.gradle`", it is ours.26- **`cicd-standards` (critical boundary, in both directions)**: **pipeline strategy, the27 gates, artifact signing, the SBOM, OIDC, runner security and isolation are theirs**.28 **How the `Jenkinsfile` is written as Groovy code** — declarative vs *scripted*, CPS, `@NonCPS`,29 Shared Libraries, the Script Security sandbox — **is ours**. A "the pipeline does not30 pass the gate" problem is theirs; a "the pipeline does not serialise and fails at step 3" one is ours.31- `scala-standards` and `clojure-standards` (the other JVM languages), `iac-standards`,32 `container-runtime-security-standards` (the isolation of the container where the agent runs),33 `lua-standards` and `perl-standards` (nothing in common), `appsec-standards`,34 `vulnerability-management-standards`, `secrets-management-standards`, `sql-standards`,35 `observability-standards`.3637## 2. Default decisions3839> Verify the latest version on the web before pinning it in a real project (§8).4041**Apache Groovy is alive and actively maintained** — a relevant fact because the opposite is often42assumed. It is an **Apache Software Foundation** project (Apache-2.0) and as of Aug 2026 it maintains **three43parallel lines**, with releases from the same week:4445| Line | Verified status (Aug 2026) |46|---|---|47| **Groovy 5.0.x** | **Current stable**. 5.0.8 (2026-07-29) |48| Groovy 4.0.x | Maintained. 4.0.33 (2026-07-29) |49| Groovy 6.0.0 | **BETA-1 (2026-08-01)**. Do not use in production |5051**Groovy 5 JDK requirements** (official release notes): *JDK17+ to **build** Groovy* and52**JDK 11 is the minimum supported JRE version**; tested on JDK 11–25. In practice: you do not choose the53Groovy version if you work inside Gradle or Jenkins — **the tool imposes it**,54because it embeds its own Groovy. Check which one before using a language feature.5556| Piece | Choice | Verified | Licence (raw LICENSE) |57|---|---|---|---|58| Language | Apache Groovy 5.0.x | 5.0.8 | **Apache-2.0** |59| Build | Gradle 9.x | 9.6.1 (2026-06-26); 9.7 in RC | **Apache-2.0** |60| CI | Jenkins LTS | 2.568.x line; *weekly* 2.575 | **MIT** |61| Tests | **Spock** | **2.4** (2025-12-11), stable after a long series of *milestones* | **Apache-2.0** |62| Lint | CodeNarc | see §8 (not verified) | see §8 |6364### Gradle: `build.gradle` versus `build.gradle.kts`6566The real criteria today, unambiguously:6768- **New project → Kotlin DSL (`.kts`).** It is what Gradle generates by default. Verbatim from the release69 notes of version 8.2: *"Kotlin DSL is now the default option when generating a new project with the70 init task."* An engineering reason, not a fashion one: static typing, real autocompletion and navigation71 in the IDE, and compile-time errors in the build itself. The Groovy DSL is dynamic and its72 "API" is metaprogramming and delegation: the IDE guesses.73- **The Groovy DSL is NOT deprecated** and remains supported. There is no end-of-life pressure.74- **Existing `build.gradle` → do not migrate it for the sake of it.** Migrating a large build is a75 project with real risk (plugins, `ext`, closures with implicit delegation, ad-hoc logic in76 loose `.gradle` files) and its benefit is *developer experience*, not functionality. Criteria: migrate if you are already77 going to touch the build in depth, if the build is complex enough that the lack of types78 costs real time, or if you are going to move it to `buildSrc`/*convention plugins* (where the Kotlin DSL gains79 much more). Otherwise, it stays and is kept well written.80- Whichever the DSL: **build logic outside the script**. `buildSrc/` or an *included build* with81 *convention plugins*; `build.gradle(.kts)` must be declarative. Version catalog82 (`gradle/libs.versions.toml`) as the single source of versions. **Gradle Wrapper committed, with83 `gradle-wrapper.properties` pointing to a distribution with `distributionSha256Sum`** — a wrapper84 without a checksum is execution of downloaded code without verification.8586## 3. Jenkins: the `Jenkinsfile` is not normal Groovy8788This is the section that prevents the most incidents. **A `Jenkinsfile` looks like Groovy and does not behave like89Groovy.**9091- **CPS transformation**: Jenkins compiles the pipeline with the Groovy parser but **does not execute its92 bytecode**: a `CompilationCustomizer` rewrites almost every operation into93 *continuation-passing* style, and its own interpreter executes them. Reason: **the complete program state94 is serialised to disk** (`program.dat` in the build directory) on every asynchronous95 operation, so that the build survives a Jenkins restart and continues where it was.96- **Consequences that must be internalised**:97 - Every variable live at the point of a *step* call **must be serialisable**. A regex `Matcher`,98 an `InputStream`, a `File` → `NotSerializableException` at an apparently99 random point. Rule: non-serialisable objects live and die inside a `@NonCPS`, or are100 discarded (`m = null`) before the next step.101 - **It is slow**, and the official documentation is explicit: it is not meant to be efficient and must102 be limited to high-level *glue code*, with the real logic in external programs via `sh`/`bat`.103 **A loop over 10,000 items inside a pipeline is a design error.** There are also Groovy104 constructs that the interpreter does not cover well (closures over collections, inheritance).105- **`@NonCPS`**: marks a method so that it is compiled and executed with native Groovy semantics (except for the106 sandbox checks). Hard rules:107 - **You cannot call CPS methods or pipeline *steps* from a `@NonCPS`.** Its only108 correct use is pure computation: serialisable data in, serialisable summary out.109 - Do not accept, return or store non-serialisable values from a `@NonCPS`.110 - `@Override`s of methods of binary classes (`toString()`, `equals()`) **must** be `@NonCPS`,111 because the binary code will call them from a non-CPS context.112 - Jenkins detects the most common error (calling CPS code from a non-CPS context) and warns about it in the113 log; that warning **is a failure, not noise**.114- **Declarative by default.** `pipeline { agent … stages { … } }` is the correct form for 95% of115 cases: fixed structure, validatable, with native `post`, `options`, `environment` and `matrix`, and116 much less loose Groovy to serialise. **Scripted** (`node { … }`) only for control flows117 that declarative cannot express, and even then a bounded `script { }` block inside the118 declarative is preferred over an entire *scripted* pipeline.119- **Shared Libraries** for everything repeated across repos: `vars/` (global steps, one file per120 step, with `call()`) and `src/` (normal Groovy classes, here you can indeed type and test). The library121 is **a repo versioned with tags**, and pipelines reference it **by tag or by commit**, never122 by a moving branch: `@Library('mylib@v3.2.0')`. A library referenced by `master` is a global123 change without review that applies to all pipelines at once.124 - It is tested: the `src/` classes with Spock, in their own build. A pipeline whose only test125 environment is production is not code, it is a bet.126- **`Jenkinsfile` short by design**: it orchestrates, it does not implement. Any logic longer than a few lines127 goes down to a script in the repo (`bash`/`python`) or to the Shared Library. Double benefit: it can be128 run and tested locally without Jenkins, and it does not go through CPS.129130## 4. The language: what to use and what to avoid131132- **`@CompileStatic` whenever possible; `@TypeChecked` at a minimum.** Dynamic Groovy pays for every133 call through runtime *dispatch* and does not detect a method typo until the line is executed — in134 a pipeline, that is "fails at minute 40 of the build". Apply it at class level in all the logic135 of a Shared Library (`src/`) and in any Groovy application code.136 - Real limit and why it is not applied everywhere: **the Gradle and Jenkins DSLs depend on137 dynamic metaprogramming** (delegation, `methodMissing`, `propertyMissing`), so the pipeline script138 and the `build.gradle` **cannot be `@CompileStatic`**. That is exactly the reason139 why the logic must leave the script and go down to classes that can be.140- **Closures and delegation** are the mechanism of the DSLs: `delegate`, `owner`, `resolveStrategy`.141 When you write your own DSL, annotate **`@DelegatesTo`** on the closure parameter — without that, neither the IDE142 nor `@CompileStatic` can resolve anything inside the block, and your DSL is untouchable.143- **AST transforms**: `@Immutable`, `@Canonical`, `@Builder`, `@Memoized`, `@Slf4j` are useful and144 legitimate. Writing **your own AST transforms** is vetoed except in extraordinary cases: they are code that145 runs in the compiler, almost impossible to debug and that breaks with every Groovy version.146- **GString and its interpolation trap** — two classic incidents, both vetoed:147 - **In SQL**: `sql.execute("SELECT * FROM t WHERE id = $id")` with `groovy.sql.Sql` **does**148 parameterise the GString (it is a deliberate special case), but149 `sql.execute("SELECT * FROM t WHERE id = ${id}".toString())` **does not**: converting to `String`150 loses the parameterisation and you have injection. Any concatenation or `.toString()` on a151 GString destined for SQL is vetoed. Use explicit placeholders and a parameter list.152 - **In logs**: `log.debug("payload: $obj")` **always evaluates the interpolation**, even if the DEBUG153 level is off. A hot-path cost and, worse, **leakage**: in a pipeline that can dump a154 credentials object into the build log. Use the logger's placeholder form, and never155 interpolate credential objects or environment maps.156- `?.` (*safe navigation*) and `?:` (Elvis) are idiomatic and correct; deeply chained `?.`157 is not robustness, it is hiding a `null` that should not exist — fail early at the edge.158- `def` versus types: with `@CompileStatic`, explicit types in public signatures always. `def` only159 for obvious locals.160- Groovy truth: `0`, `""`, `[]`, `[:]` and `null` are false. **It is not Java nor Lua**; an `if (x)`161 check on a number or a collection does not mean "it is not null".162163## 5. Security: Groovy is arbitrary code execution on the JVM164165**Starting point without nuance: a `Jenkinsfile` or a `build.gradle` from an untrusted repository166executes whatever it wants inside your runner, with your runner's identity.** It is not a configuration167file; it is a program. Everything that follows derives from that.168169- **Gradle**: `./gradlew build` on an unknown repo executes its build code before compiling170 anything. A PR that modifies `build.gradle`, `settings.gradle` or `buildSrc/` is a PR that modifies what171 your CI executes. Consequence: **workflows that run builds from forks never receive secrets172 nor tokens with write permissions**; Gradle plugins are pinned to an exact version from the173 version catalog and resolved from an own repository or a mirror, never from an arbitrary174 repository declared in the script itself; the wrapper carries `distributionSha256Sum`.175- **Jenkins Script Security**: the Groovy sandbox is what prevents a pipeline from taking control of the176 controller. Its model is an allowlist of methods, with **manual approval** (*In-process Script177 Approval*) for whatever falls outside. Two rules:178 - **FORBIDDEN to disable the sandbox.** The Pipeline runtime has access to Jenkins179 internals: retrieving encrypted credentials, triggering deployments, deleting artifacts. A pipeline without180 a sandbox is full control of the controller for anyone who can write to the repo.181 - **Approving a method is a security decision, not an administrative unblock.** Approving things182 like `getClass`, `getDeclaredMethods`, anything reflection-related or `System.*` amounts to183 disabling the sandbox through the back door. If a pipeline needs something the sandbox does not184 allow, **the correct solution is to move it down to an approved Shared Library or to a plugin**, not185 to approve the method.186 - There have been and will be **sandbox bypass CVEs** (implicit casts of the Groovy runtime,187 default parameter expressions in CPS methods, rebuild of an unapproved script). Corollary:188 the sandbox is **containment, not a boundary**. The boundary is who can write to the repo and what189 the agent can reach.190- **Controller and agent hygiene**: nothing is compiled or executed on the controller — everything on191 ephemeral agents. The Jenkins script console (`/script`) is arbitrary execution as the192 Jenkins user: restricted and audited access.193- **Secrets**: through credentials binding (`withCredentials`), never in the `Jenkinsfile`, in194 the repo's `gradle.properties` nor in global environment variables. `set -x` in a `sh` and a195 GString interpolation are the two usual ways a secret ends up in the build log.196- **Runner supply chain** — the control that actually cuts. Precedent already established in this197 catalogue and verified: in 2026 **malicious packages published with cryptographically valid198 SLSA Build L3 attestations** were documented (the worm over 42 `@tanstack` packages in May 2026,199 and the wave over `@redhat-cloud-services` in June), because the attackers hijacked the200 *legitimate pipeline* and the provenance correctly described a compromised build. **Signed201 provenance is no longer enough**: it says where the artifact came from, not that it is safe. What does cut202 the vector, applied to the Groovy/CI world:203 - **Pin by SHA or by digest everything that enters the runner**: GitHub Actions by commit SHA204 (not by tag), agent images by digest, Gradle distribution by `distributionSha256Sum`,205 plugins and dependencies by exact version with checksum/signature verification206 (`gradle/verification-metadata.xml`), Shared Libraries by immutable tag or commit.207 - **No dependency resolution from an arbitrary repository** declared in the build script208 itself.209 - Ephemeral runner, without long-lived credentials, with bounded egress, and OIDC trust tied to a210 **specific workflow and protected branch**, not to "the repository". The full strategy lives in211 `cicd-standards`; what is ours is that **the Groovy file is the vector**.212213## 6. Performance and operability214215- In a pipeline the dominant cost is almost always the CPS interpreter: the lever is not optimising216 Groovy, it is **taking work out of the pipeline** towards `sh`/scripts on the agent.217- Gradle: build cache and configuration cache enabled; `--scan`/Develocity to know where the218 time goes before touching anything. Custom tasks with declared inputs/outputs (otherwise they are neither219 incremental nor cacheable). No logic in the configuration phase.220- Jenkins: `timeout` on every stage (**a pipeline without a timeout is an agent blocked forever**),221 `retry` only on idempotent steps, `disableConcurrentBuilds()` where there is shared state, and222 `buildDiscarder` — `program.dat` files and artifacts fill disks.223- Observability of CI as a service: duration per stage, failure and *flakiness* rate, time in224 queue. A pipeline without metrics cannot be improved (telemetry, in `observability-standards`).225- The same command must run locally and in CI. If a build only works on the agent, it is a build bug.226227## 7. Sustainability and prohibitions228229- **Cadence**: Gradle follows a fast cadence — update the wrapper regularly and read the230 *release notes* first (Gradle major jumps do break plugins). Jenkins: use the **LTS231 line**, with monthly updates and **plugin updates as a recurring task**, not annual: the232 majority of Jenkins security advisories are about plugins.233- A Jenkins plugin with no releases and no maintainer is security debt: it is replaced or retired.234 Audit the list of installed plugins once a quarter and **delete what is not used** (each235 plugin is attack surface on the controller).236- Own deprecations in a Shared Library: versioned with tags and with a retirement window; the237 pipelines pointing to an old tag are visible debt, which is exactly what is wanted.238239**List of prohibitions (veto):**240- ❌ **FORBIDDEN** to disable the Jenkins Script Security sandbox.241- ❌ **FORBIDDEN** to approve reflection methods, `System.*`, `getClass`/`getDeclaredMethods` or similar242 in the *script approval*. If that is needed, the design is wrong.243- ❌ **FORBIDDEN** to run builds from forks or from untrusted repos with access to secrets or to244 credentials with write permissions.245- ❌ Referencing a Shared Library, an action or an image by branch or moving tag. Immutable tag, commit246 or digest.247- ❌ Gradle wrapper without `distributionSha256Sum`; dependencies without checksum/signature verification.248- ❌ Non-trivial logic inside the `Jenkinsfile` or the `build.gradle`: move it down to a Shared Library,249 `buildSrc`/convention plugin, or a script executable on the agent.250- ❌ Loops over large collections or real computation inside a CPS pipeline.251- ❌ Calling pipeline *steps* from a `@NonCPS` method; keeping non-serialisable objects alive252 across a step.253- ❌ GString converted to `String` in a SQL query; interpolation of credential objects or of the254 environment in logs.255- ❌ Dynamic Groovy (without `@CompileStatic`) in application code or in the `src/` of a Shared Library.256- ❌ Own AST transforms except with extraordinary written justification.257- ❌ Migrating a large and healthy `build.gradle` to `.kts` with no reason other than fashion.258- ❌ Running code on the Jenkins controller instead of on ephemeral agents.259- ❌ Pipeline without `timeout` or `buildDiscarder`.260- ❌ **Choosing Groovy for new application code.** Groovy is the correct choice in three places:261 a `build.gradle` that already exists, a `Jenkinsfile`, and a **Spock** suite (which remains an262 excellent test framework and a fully legitimate use, even for testing Java or Kotlin code).263 Outside that, a new service on the JVM is written in Java or Kotlin — static typing, tooling,264 performance, hiring and ecosystem all play against Groovy, and the project itself265 positions itself as a *scripting* and DSL language, not as an application platform.266267## 8. Mandatory web verification268269Before pinning versions or APIs, **verify online** (WebSearch/WebFetch and the Atom feeds270`https://github.com/OWNER/REPO/releases.atom`; `api.github.com` returns 403 unauthenticated):2711. **Groovy**: latest 5.0.x and the status of **Groovy 6** (as of Aug 2026 at `6.0.0-BETA-1`: **not production**),272 and the JDK matrix of the version you are going to use at `groovy-lang.org/releasenotes/`. Verified as of273 Aug 2026 for Groovy 5: **JDK 11 minimum JRE, JDK 17+ to build Groovy, tested up to JDK 25**.2742. **Which Groovy your tool embeds** (Gradle and Jenkins ship their own, usually older than275 the latest stable): it is what decides which language features you can use.2763. **Gradle**: latest stable (9.6.1 as of Aug 2026, with 9.7 in RC) and the release notes before277 bumping the wrapper. Confirm that the Groovy DSL is still **supported and not deprecated**.2784. **Jenkins**: the number of the current LTS and, above all, the **security advisories** of Jenkins and279 its plugins (`jenkins.io/security/advisories`) — including the Groovy sandbox *bypasses*.2805. **Spock**: latest stable (2.4 as of Aug 2026) and its declared compatibility with your Groovy281 and JUnit Platform versions.2826. CVEs of Groovy, Gradle and the Jenkins plugins in use (osv.dev / GitHub Advisories).283284**Declared gaps as of Aug 2026** (do not fill in from memory):285- **CodeNarc**: current version, licence and maintenance status **not verified**. Before pinning it286 as the default linter in a project, check its latest release and its raw `LICENSE`. If it is287 stalled, the Groovy quality gate rests on `@CompileStatic` + compilation + the tests, which288 is where the real value is anyway.289290**Declared discrepancy**: the *What's new in Gradle 9* page on gradle.org places the adoption of the291Kotlin DSL as the recommended option in **"April 2023 (Gradle 8.2)"**, whereas the release notes of292version 8.2 on `docs.gradle.org` **show no publication date** confirming it and the real date293of 8.2 is not verified. What is verified verbatim in the 8.2 notes is the substantive294claim: *"Kotlin DSL is now the default option when generating a new project with the init task."*295Use the claim, not the date.296297If the web contradicts this document, **the web wins** — flag the discrepancy.