Skip to content

null-markeder

CI Coverage Status Maven Central Javadoc License Mutation Score

A library and sample test to ensure that each package in your java project contains a package-info.java file with the jspecify @NullMarked annotation.

Website

Usage

Add dependency

<dependency>
    <groupId>li.selman</groupId>
    <artifactId>null-markeder</artifactId>
    <version>VERSION</version>
    <scope>test</scope>
</dependency>

You'll also need archunit-junit5, jspecify and assertj-core (as test scope) in your own project if you don't already have them.

Add an ArchUnit test

@AnalyzeClasses(packages = "com.example", importOptions = ImportOption.DoNotIncludeTests.class)
class ArchitectureTest {

    private static final String ROOT_PACKAGE = "com.example";

    /** Enforce that all packages contain a `package-info.java` annotated with `@NullMarked`.
     * Run {@link PackageInfoGenerator} to fix. */
    @ArchTest
    void packagesShouldBeAnnotated(JavaClasses classes) throws IOException {
        var rootPackage = classes.getPackage(ROOT_PACKAGE);
        // getSubpackagesInTree() excludes rootPackage itself, so it must be checked separately or a
        // package with no subpackages would never be checked.
        List<String> violations = Stream.concat(Stream.of(rootPackage), rootPackage.getSubpackagesInTree().stream())
                .filter(pkg -> !pkg.isAnnotatedWith(NullMarked.class))
                .map(pkg -> pkg.getDescription() + " is not annotated with @" + NullMarked.class.getSimpleName())
                .toList();

        if (!violations.isEmpty()) {
            PackageInfoGenerator.main(ROOT_PACKAGE);
        }

        assertThat(violations)
                .as("Not all packages contain a package-info.java file with the required nullability annotations. "
                        + "Ran PackageInfoGenerator to fix - re-run the build.")
                .isEmpty();
    }
}

Building

./mvnw verify

Test coverage is enforced at 100% (line and branch) via JaCoCo; verify fails if it drops below that. Run open target/site/jacoco/index.html after a build to see the report.

verify also runs Spotless (palantir-java-format + sorted pom.xml), Checkstyle, and Error Prone/NullAway via the compiler plugin. Run ./mvnw spotless:apply to auto-format before committing.

Mutation Testing

Mutation Score

Line/branch coverage only proves a test executed some code, not that it would notice a bug in it. PIT mutation testing seeds small deliberate bugs ("mutants") into the compiled classes and checks whether the test suite actually fails for each one; a mutant that survives is a gap in the tests.

Mutation testing runs nightly at around 02:00 UTC via .github/workflows/pit-mutation-testing.yml, and only when at least one new commit has landed on main since the last successful run - so it stays off the critical path for every push/PR while still picking up changes automatically. It can also be triggered manually from the Actions tab.

See the full HTML mutation report for a per-class, per-mutator breakdown, or run it locally with:

./mvnw test-compile org.pitest:pitest-maven:mutationCoverage
open target/pit-reports/index.html

Releasing

Releases are published to Maven Central via JReleaser. Pushing a tag matching v* (e.g. v1.0.0) triggers .github/workflows/release.yml, which stages the build artifacts and hands them to JReleaser to sign and deploy to the Central Portal.

./bumpPomVersion.sh
git push
./release.sh

Contributing

Bug reports, feature requests and pull requests are welcome — see CONTRIBUTING.md. This project follows a Code of Conduct; by participating you agree to abide by it.

License

null-markeder is licensed under the Apache License, Version 2.0.

See jreleaser.yml for the deployment configuration.

About

Ensure every package in a Java project has a package-info.java annotated with JSpecify's @NullMarked

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages