With the release of JUnit 6.0.0, the maintainers adopted JSpecify nullability annotations across all JUnit modules. It means that method parameters, return types, and other API elements in JUnit are now annotated to explicitly declare whether they accept or return null.
Thus, tests (or extensions) are safer, and you get compile-time or IDE-time hints instead of relying solely on runtime behavior.
If we annotate a parameter as
@NonNulland accidentally passnullat runtime, the program will still run and may throw a NullPointerException only when we try to use that null value.The annotation itself does not insert any null checks into the method.
1. Null‑related Annotations (via JSpecify in JUnit 6)
We can make use of the following annotation to specify the null-related behavior of the tests:
| Annotation | Description / Purpose |
|---|---|
@Nullable | – This marks a type of use (parameter type, return type, field type, etc.) as nullable. – This value may be null. – This informs IDEs/tools/static‑analysis and helps users know null‑safety contract. |
@NonNull | – This marks a type of use as non-null. – This value must not be null. – This ensures that callers / implementations treat it as non-null, giving stronger guarantees. |
@NullMarked | – This is a package‑/class‑/module-level annotation. – This indicates that within the annotated scope, unannotated types should be treated as non-null by default (unless explicitly marked @Nullable). – This helps reduce verbosity i.e. instead of marking every type as non-null, we mark only nullable ones. |
Using
@NullMarkedcan reduce annotation clutter. Instead of marking every non-null type explicitly, we can annotate whole classes/packages/modules and then only mark those that are nullable.
2. JUnit 6 @Nullable Example
In JUnit 6, @Nullable comes from the JSpecify annotations that JUnit 6 adopts to indicate whether a parameter or return value can be null. This doesn’t change test execution, but it helps IDE inspections, static analysis, and Kotlin interop.
Here’s a simple example showing @Nullable in a test:
import org.junit.jupiter.api.Test;
import org.jspecify.annotations.Nullable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class NullableExampleTest {
// A simple method that may return null
@Nullable
String getNullableString(boolean returnNull) {
if (returnNull) {
return null;
} else {
return "Hello, JUnit 6!";
}
}
@Test
void testNullableReturn() {
// Test case 1: method returns a string
String result1 = getNullableString(false);
assertEquals("Hello, JUnit 6!", result1);
// Test case 2: method returns null
String result2 = getNullableString(true);
assertNull(result2); // @Nullable tells the IDE/static analysis that this can be null
}
@Test
void testNullableParameter() {
// @Nullable can also be used on parameters
printMessage(null);
printMessage("JUnit 6 is great!");
}
void printMessage(@Nullable String message) {
if (message == null) {
System.out.println("No message provided");
} else {
System.out.println(message);
}
}
}
The program output:
No message provided
JUnit 6 is great!
3. JUnit 6 @NonNull Example
In JUnit 6, @NonNull indicates that a method must not return null or a parameter must not be null. IDEs, static analysis tools, and Kotlin interop use this metadata to catch potential null-safety violations at compile time.
Here’s a simple JUnit 6 example:
import org.junit.jupiter.api.Test;
import org.jspecify.annotations.NonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
class NonNullExampleTest {
// Method guarantees a non-null return value
@NonNull
String getNonNullMessage(boolean condition) {
if (condition) {
return "JUnit 6 rocks!";
} else {
return "Null is not allowed!"; // must return non-null
}
}
@Test
void testNonNullReturn() {
String result = getNonNullMessage(true);
assertEquals("JUnit 6 rocks!", result);
String result2 = getNonNullMessage(false);
assertEquals("Null is not allowed!", result2);
}
@Test
void testNonNullParameter() {
printMessage("Hello, JUnit 6!");
// printMessage(null); // IDE/static analyzer will warn: null not allowed
}
// Parameter marked @NonNull → must not be null
void printMessage(@NonNull String message) {
// No need to check for null; tools assume non-null
System.out.println(message.toUpperCase());
}
}
The program output:
HELLO, JUNIT 6!
4. JUnit 6 @NullMarked Example
In JUnit 6, @NullMarked is a package‑, class‑, or module‑level annotation that indicates all unannotated types in the scope are non-null by default. You only need to annotate values that can be null with @Nullable. This reduces boilerplate and makes null-safety easier to manage.
Here’s a JUnit 6 example using @NullMarked:
import org.jspecify.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@NullMarked // All unannotated types in this class are treated as @NonNull
class NullMarkedExampleTest {
// Return type is non-null by default because of @NullMarked
String getMessage(boolean condition) {
return condition ? "JUnit 6 is awesome!" : "Null is not allowed!";
}
// Parameter is non-null by default
void printMessage(String message) {
System.out.println(message.toUpperCase());
}
// Only mark explicitly nullable parameters/returns
@Nullable
String getNullableMessage(boolean condition) {
return condition ? "Some message" : null; // nullable allowed
}
@Test
void testNonNullBehavior() {
String msg = getMessage(true);
assertEquals("JUnit 6 is awesome!", msg);
// IDE or static analysis will warn if you try to pass null here:
// printMessage(null); // ❌ warning
printMessage(msg); // ✅ OK
}
@Test
void testNullableBehavior() {
String maybeNull = getNullableMessage(false);
assertNull(maybeNull); // ✅ allowed because @Nullable
}
}
The program output:
JUNIT 6 IS AWESOME!
5. Conclusion
JUnit 6’s adoption of JSpecify nullability annotations @Nullable, @NonNull, and @NullMarked represents a major improvement in type safety and developer guidance. While these annotations do not enforce runtime null checks, they significantly improve code safety, maintainability, and readability, helping developers write more reliable and expressive tests.
Happy Learning !!
Comments