Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/language/default-interface-methods.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion content/tooling/aot-class-preloading.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
58 changes: 58 additions & 0 deletions content/tooling/junit6-with-jspecify.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}