This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 25b5111 add GROOVY-11894
25b5111 is described below
commit 25b5111ade1884d2cbec82f01c826d71c8200815
Author: Paul King <[email protected]>
AuthorDate: Sun Apr 5 15:51:50 2026 +1000
add GROOVY-11894
---
site/src/site/releasenotes/groovy-6.0.adoc | 141 +++++++++++++++++++++++++++++
1 file changed, 141 insertions(+)
diff --git a/site/src/site/releasenotes/groovy-6.0.adoc
b/site/src/site/releasenotes/groovy-6.0.adoc
index c353de9..451764d 100644
--- a/site/src/site/releasenotes/groovy-6.0.adoc
+++ b/site/src/site/releasenotes/groovy-6.0.adoc
@@ -612,6 +612,147 @@ measure over the two input list sizes, giving us
confidence that
the loop terminates: on each iteration at least one input list
shrinks, and they can never grow.
+== Optional Null Checking
+
+Groovy's type checking is extensible, allowing you to strengthen
+type checking beyond what the standard checker provides.
+Groovy 6 adds two optional null-safety type checkers
+(https://issues.apache.org/jira/browse/GROOVY-11894[GROOVY-11894])
+that detect null-related errors at compile time.
+
+=== NullChecker
+
+The `NullChecker` extension validates code annotated with
+`@Nullable`, `@NonNull`, and `@MonotonicNonNull` annotations.
+It recognizes these annotations by simple name from any package
+(JSpecify, JSR-305, JetBrains, SpotBugs, Checker Framework, or your own).
+
+A simple example showing a null guard:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.NullChecker')
+int safeLength(@Nullable String text) {
+ if (text != null) {
+ return text.length() // ok: null guard
+ }
+ return -1
+}
+
+assert safeLength('hello') == 5
+assert safeLength(null) == -1
+----
+
+Without the null guard, dereferencing a `@Nullable` parameter
+produces a compile-time error:
+
+[source,groovy]
+----
+def greet = { @Nullable String name ->
+ name.toUpperCase() // potential null
dereference
+}
+----
+
+----
+[Static type checking] - Potential null dereference: 'name' is @Nullable
+----
+
+The checker also recognises safe navigation and early-exit patterns:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.NullChecker')
+String process(@Nullable String input) {
+ if (input == null) return 'default' // early exit
+ input.toUpperCase() // ok: input is non-null
here
+}
+----
+
+Passing `null` to a `@NonNull` parameter, returning `null` from a
+`@NonNull` method, or assigning `null` to a `@NonNull` field are
+all flagged:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.NullChecker')
+class Greeter {
+ @NonNull String name
+
+ @NonNull static String greet(@NonNull String name) {
+ return null // Cannot return null from
@NonNull method
+ }
+ static void main(String[] args) {
+ greet(null) // Cannot pass null to
@NonNull parameter
+ }
+}
+----
+
+The `@MonotonicNonNull` annotation marks fields that start as `null`
+but, once assigned a non-null value, must never become `null` again --
+useful for lazy initialisation:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.NullChecker')
+class LazyService {
+ @MonotonicNonNull String cachedValue
+
+ String getValue() {
+ if (cachedValue != null) {
+ return cachedValue.toUpperCase() // ok: null guard
+ }
+ cachedValue = 'computed'
+ return cachedValue.toUpperCase() // ok: just assigned
non-null
+ }
+}
+----
+
+The `@NullCheck` AST transform also integrates with the checker --
+applying `@NullCheck` to a class makes all parameters implicitly
+`@NonNull`, and the checker enforces this at compile time:
+
+[source,groovy]
+----
+@NullCheck
+@TypeChecked(extensions = 'groovy.typecheckers.NullChecker')
+class Greeter {
+ static String greet(String name) {
+ "Hello, $name!"
+ }
+ static void main(String[] args) {
+ greet(null) // caught at compile time
+ }
+}
+----
+
+=== StrictNullChecker
+
+The `StrictNullChecker` extends the annotation-based checks with
+flow-sensitive analysis. It tracks variables assigned `null`,
+left uninitialized, or resulting from expressions with a `null` branch,
+and flags their dereferences -- even without annotations:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.StrictNullChecker')
+static main(args) {
+ def x = null
+ x.toString() // Potential null
dereference: 'x' may be null
+}
+----
+
+It also recognises when a variable is reassigned to a non-null value:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.StrictNullChecker')
+static main(args) {
+ def x = null
+ x = 'hello'
+ assert x.toString() == 'hello' // ok: reassigned non-null
+}
+----
+
== Under exploration