The problem
JSpecify's @NullMarked only protects a package if its package-info.java actually
carries the annotation. It's easy to add a new package, forget the file, and quietly lose null-safety
coverage for everything in it — no compiler error, no warning, just a gap.
What it does
Detects
An ArchUnit test fails the build the moment a package is missing @NullMarked.
Fixes
PackageInfoGenerator generates or updates every missing package-info.java for you.
Stays out of the way
Test-scoped dependency, no runtime footprint, no annotation processor.
Usage
Add the dependency (see latest release for the version):
<dependency>
<groupId>li.selman</groupId>
<artifactId>null-markeder</artifactId>
<version>VERSION</version>
<scope>test</scope>
</dependency>
Then add an ArchUnit test that fails (and self-heals) when a package is missing the annotation:
@ArchTest
void packagesShouldBeAnnotated(JavaClasses classes) throws IOException {
var rootPackage = classes.getPackage(ROOT_PACKAGE);
List<String> violations = rootPackage.getSubpackagesInTree().stream()
.filter(pkg -> !pkg.isAnnotatedWith(NullMarked.class))
.map(pkg -> pkg.getDescription() + " is not annotated with @NullMarked")
.toList();
if (!violations.isEmpty()) {
PackageInfoGenerator.main(ROOT_PACKAGE);
}
assertThat(violations).isEmpty();
}
Full walkthrough, including the required test dependencies, is in the README.