Tooling Intermediate

JUnit 6 adopts JSpecify @NullMarked, making null contracts explicit across its assertion API.

โœ• JUnit 5
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class UserServiceTest {

    // JUnit 5: no null contracts on the API
    // Can assertEquals() accept null? Check source...
    // Does fail(String) allow null message? Unknown.

    @Test
    void findUser_found() {
        // Is result nullable? API doesn't say
        User result = service.findById("u1");
        assertNotNull(result);
        assertEquals("Alice", result.name());
    }

    @Test
    void findUser_notFound() {
        // Hope this returns null, not throws...
        assertNull(service.findById("missing"));
    }
}
โœ“ JUnit 6
import org.junit.jupiter.api.Test;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import static org.junit.jupiter.api.Assertions.*;

@NullMarked  // all refs non-null unless @Nullable
class UserServiceTest {

    // JUnit 6 API is @NullMarked:
    // assertNull(@Nullable Object actual)
    // assertEquals(@Nullable Object, @Nullable Object)
    // fail(@Nullable String message)

    @Test
    void findUser_found() {
        // IDE warns: findById returns @Nullable User
        @Nullable User result = service.findById("u1");
        assertNotNull(result); // narrows type to non-null
        assertEquals("Alice", result.name()); // safe
    }

    @Test
    void findUser_notFound() {
        @Nullable User result = service.findById("missing");
        assertNull(result); // IDE confirms null expectation
    }
}
See a problem with this code? Let us know.
๐Ÿ“œ

Explicit contracts

@NullMarked on the JUnit 6 module documents null semantics directly in the API โ€” no source-reading required.

๐Ÿ›ก๏ธ

Compile-time safety

IDEs and analyzers warn when null is passed where non-null is expected, catching bugs before tests run.

๐ŸŒ

Ecosystem standard

JSpecify is adopted by Spring, Guava, and others โ€” consistent null semantics across your whole stack.

Old Approach
Unannotated API
Modern Approach
@NullMarked API
Since JDK
17
Difficulty
Intermediate
JUnit 6 with JSpecify null safety
Available

Available since JUnit 6.0 (October 2025, requires Java 17+)

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.

Share ๐• ๐Ÿฆ‹ in โฌก