From e5e7bf9150b46e69f3c7a7928029e17f42269ebd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:06:19 +0000 Subject: [PATCH 1/2] Initial plan From 419216563f30a63ae74315d3b3eba1dde442776d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:11:15 +0000 Subject: [PATCH 2/2] Add JUnit 6 with JSpecify null safety pattern to tooling category Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- .../language/default-interface-methods.json | 2 +- content/tooling/aot-class-preloading.json | 2 +- content/tooling/junit6-with-jspecify.json | 58 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 content/tooling/junit6-with-jspecify.json diff --git a/content/language/default-interface-methods.json b/content/language/default-interface-methods.json index 406a030..1974a97 100644 --- a/content/language/default-interface-methods.json +++ b/content/language/default-interface-methods.json @@ -34,7 +34,7 @@ "state": "available", "description": "Available since JDK 8 (March 2014)." }, - "prev": "tooling/aot-class-preloading", + "prev": "tooling/junit6-with-jspecify", "next": "language/markdown-javadoc-comments", "related": [ "language/private-interface-methods", diff --git a/content/tooling/aot-class-preloading.json b/content/tooling/aot-class-preloading.json index c3edceb..d07af7f 100644 --- a/content/tooling/aot-class-preloading.json +++ b/content/tooling/aot-class-preloading.json @@ -35,7 +35,7 @@ "description": "Available as a standard feature in JDK 25 LTS (JEPs 514/515, Sept 2025)." }, "prev": "tooling/built-in-http-server", - "next": "language/default-interface-methods", + "next": "tooling/junit6-with-jspecify", "related": [ "tooling/jshell-prototyping", "tooling/single-file-execution", diff --git a/content/tooling/junit6-with-jspecify.json b/content/tooling/junit6-with-jspecify.json new file mode 100644 index 0000000..d51586a --- /dev/null +++ b/content/tooling/junit6-with-jspecify.json @@ -0,0 +1,58 @@ +{ + "id": 111, + "slug": "junit6-with-jspecify", + "title": "JUnit 6 with JSpecify null safety", + "category": "tooling", + "difficulty": "intermediate", + "jdkVersion": "17", + "oldLabel": "JUnit 5", + "modernLabel": "JUnit 6", + "oldApproach": "Unannotated API", + "modernApproach": "@NullMarked API", + "oldCode": "import org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass UserServiceTest {\n\n // JUnit 5: no null contracts on the API\n // Can assertEquals() accept null? Check source...\n // Does fail(String) allow null message? Unknown.\n\n @Test\n void findUser_found() {\n // Is result nullable? API doesn't say\n User result = service.findById(\"u1\");\n assertNotNull(result);\n assertEquals(\"Alice\", result.name());\n }\n\n @Test\n void findUser_notFound() {\n // Hope this returns null, not throws...\n assertNull(service.findById(\"missing\"));\n }\n}", + "modernCode": "import org.junit.jupiter.api.Test;\nimport org.jspecify.annotations.NullMarked;\nimport org.jspecify.annotations.Nullable;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@NullMarked // all refs non-null unless @Nullable\nclass UserServiceTest {\n\n // JUnit 6 API is @NullMarked:\n // assertNull(@Nullable Object actual)\n // assertEquals(@Nullable Object, @Nullable Object)\n // fail(@Nullable String message)\n\n @Test\n void findUser_found() {\n // IDE warns: findById returns @Nullable User\n @Nullable User result = service.findById(\"u1\");\n assertNotNull(result); // narrows type to non-null\n assertEquals(\"Alice\", result.name()); // safe\n }\n\n @Test\n void findUser_notFound() {\n @Nullable User result = service.findById(\"missing\");\n assertNull(result); // IDE confirms null expectation\n }\n}", + "summary": "JUnit 6 adopts JSpecify @NullMarked, making null contracts explicit across its assertion API.", + "explanation": "JUnit 5 shipped without standardized nullability annotations, leaving developers to guess whether assertion parameters or return values could be null. JUnit 6 adopts JSpecify across its entire module: the @NullMarked annotation makes all unannotated types non-null by default, and @Nullable marks the exceptions. The Assertions class explicitly annotates parameters such as assertNull(@Nullable Object actual) and fail(@Nullable String message), so IDEs and static analyzers like NullAway and Error Prone can catch null misuse at compile time instead of at runtime.", + "whyModernWins": [ + { + "icon": "📜", + "title": "Explicit contracts", + "desc": "@NullMarked on the JUnit 6 module documents null semantics directly in the API — no source-reading required." + }, + { + "icon": "🛡️", + "title": "Compile-time safety", + "desc": "IDEs and analyzers warn when null is passed where non-null is expected, catching bugs before tests run." + }, + { + "icon": "🌐", + "title": "Ecosystem standard", + "desc": "JSpecify is adopted by Spring, Guava, and others — consistent null semantics across your whole stack." + } + ], + "support": { + "state": "available", + "description": "Available since JUnit 6.0 (October 2025, requires Java 17+)" + }, + "prev": "tooling/aot-class-preloading", + "next": "language/default-interface-methods", + "related": [ + "enterprise/spring-null-safety-jspecify", + "errors/helpful-npe", + "errors/require-nonnull-else" + ], + "docs": [ + { + "title": "JUnit 6 Assertions API", + "href": "https://docs.junit.org/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html" + }, + { + "title": "JSpecify Nullness User Guide", + "href": "https://jspecify.dev/docs/user-guide/" + }, + { + "title": "Upgrading to JUnit 6.0", + "href": "https://github.com/junit-team/junit-framework/wiki/Upgrading-to-JUnit-6.0/Core-Principles" + } + ] +}