Skip to content

SYSTEM Cited by 1 source

Nebula ArchRules

Nebula ArchRules is a pair of Gradle plugins — the ArchRules Library Plugin and the ArchRules Runner Plugin — that lifts ArchUnit from a per-repo JUnit-suite tool into an organisation-wide rule distribution and enforcement system across Netflix's polyrepo JVM fleet ("tens of thousands of Java repositories"). Built and maintained by Netflix's JVM Ecosystem team (within Java Platform), open-sourced under github.com/nebula-plugins/nebula-archrules-plugin along with the wider Nebula plugin suite.

First documented in the 2026-05-08 Netflix TechBlog post sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules.

Operational scale (as of 2026-05-08)

"We are now running 358 (and counting) rules across over 5,000 repositories detecting over nearly 1 million issues. About 1,000 of these issues are for 'High' priority rules."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

  • Rules: 358+
  • Repositories enforcing rules: 5,000+
  • Issues detected: ~1,000,000
  • High-priority issues: ~1,000 (≈0.1% of total)
  • Polyrepo scale (Netflix Java fleet): tens of thousands of repositories

Canonical wiki instance of patterns/centralized-fleet-wide-rule-catalog and patterns/build-time-tech-debt-detection.

The forcing function

"After a Netflix incident relating to a library releasing a backwards-incompatible change, our team was asked to provide some tooling and practices to improve the Java library lifecycle management. This was not a simple case of a library making a reckless breaking change. The code removed had been deprecated for years."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

The structural problem: in a polyrepo, the library author has no central place to see who consumes their API. Standard ArchUnit (per-repo JUnit suite) doesn't solve this — it can enforce rules within a repo, not push rules from a library to all of its downstream consumers.

The two plugins

ArchRules Library Plugin (rule authoring)

Adds a new Gradle source set called archRules to a project. Inside, developers implement ArchRulesService:

public class GuavaRules implements ArchRulesService {
  static final ArchRule OPTIONAL = ArchRuleDefinition.priority(Priority.MEDIUM)
        .noClasses()
        .should()
        .dependOnClassesThat()
        .haveFullyQualifiedName("com.google.common.base.Optional")
        .because("Java Optional is preferred over Guava Optional");

    @Override
    public Map<String, ArchRule> getRules() {
        Map<String, ArchRule> rules = new HashMap<>();
        rules.put("guava optional", OPTIONAL);
        return rules;
    }
}

The plugin:

  • Auto-generates a ServiceLoader registration entry for the ArchRulesService implementation.
  • Compiles the archRules source set independently from main and test.
  • Bundles the compiled classes into a separate jar with the arch-rules classifier.
  • Publishes that jar as a separate variant with the usage attribute set to arch-rules via Gradle Module Metadata.

"This means that in order for downstream projects to use these rules, they must use Gradle Module Metadata for dependency resolution."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

ArchRules Runner Plugin (rule execution)

The Runner Plugin (consumer side) does:

  1. Source-set-aware classpath construction: "the runner plugin creates a separate configuration for each of your source sets. In each of these configurations, the archRules classpath is combined with the runtimeClasspath with the arch-rules variant selected."
  2. ServiceLoader discovery: walk the constructed classpath via ServiceLoader<ArchRulesService> to collect rules.
  3. Classpath-isolated execution: "Once the rules classpath is determined, the runner plugin will create a Gradle work action to evaluate rules against that specific source set. This action runs with classpath isolation using the archRuleRuntime configuration."*
  4. Binary-serialized output: "The action ends by writing a binary serialization of rule violations to a file for reporting."

Two flavors of rule libraries

Standalone rule libraries

"A Standalone Rule library contains no main code: only archRules. These are useful for defining rules for code you don't own, such as Core Java APIs or OSS libraries. They are also useful for generic rules that can apply to any code, such as 'don't use code marked as @Deprecated'."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

Standalone libraries can't auto-scope (no library-consumption signal); consumers add them explicitly:

dependencies {
    archRules("your:rules:1.0.0")
}

Netflix open-sources four such libraries under nebula-plugins/nebula-archrules:

  • Nullability — JSpecify @NullMarked enforcement on every public class. Smart enough to "exclude Kotlin code, as Kotlin has built-in nullability."
  • Gradle Plugin Best Practices — current-API enforcement for Gradle plugin authors (since "there are many APIs and patterns that should not be used anymore").
  • Joda / Guava Rules — discourages legacy types now superseded by java.time and standard library enhancements.
  • Security Rules — CVE detection at compile time ("detecting usage of known vulnerable APIs … in those cases, a compile time check to ensure the specific vulnerable API is not used is often good enough").

Bundled rule libraries — the central novelty

"A bundled rule library is a library with both main and archRules sources. The main source set will contain useful library code, whatever it may be. The archRules will contain rules specific to the usage of that library. For example, rules scoped to that library's package, or referencing that library's specific API. Whenever possible, we recommend writing rules in this bundled way. That is because the ArchRules Runner Plugin will be able to automatically detect these rules and run them in only the source sets that use this library as a dependency."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

Mechanic: when a project pulls in com.netflix:my-library:1.0.0 as testImplementation, the Runner sees the bundled arch-rules classifier jar in that source set's runtime classpath, registers the rules via ServiceLoader, and runs them only against that source set. No per-consumer configuration.

"In the following example, we have a Project which uses a test helper library as a testImplementation dependency, and also adds a standalone rules library to the archRules configuration. The test runtime classpath will only contain the implementation jar for the helper library, but the arch rules runtime will contain the archrules jar for the bundled rules and standalone rules. This all happens automatically."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

Reference example: Nebula Test.

Canonical wiki instance of patterns/bundled-rules-auto-scoped-to-library-consumers.

The library-lifecycle case study

The motivating use case — and the canonical instance of patterns/static-analysis-as-cross-repo-impact-discovery:

  1. Library author marks API surface with the lifecycle annotations: @Deprecated (JDK), @Public (Netflix custom), @Experimental (Netflix custom). Everything else is implicitly internal.
  2. Library author writes a bundled rule that detects callsites of @Deprecated / @Experimental / non-@Public APIs from outside the library's own package:
    ArchRuleDefinition.priority(Priority.MEDIUM)
        .noClasses().that(resideOutsideOfPackage(packageName + ".."))
        .should()
        .dependOnClassesThat(resideInAPackage(packageName + "..").and(are(deprecated())))
        .orShould().accessTargetWhere(targetOwner(resideInAPackage(packageName + ".."))
            .and(target(is(deprecated())).or(targetOwner(is(deprecated())))))
        .allowEmptyShould(true)
        .because("Deprecated APIs are subject to removal");
    
    (the deprecated() predicate comes from nebula-archrules).
  3. Runner plugin runs the rule on every consumer of the library on every main-branch CI build.
  4. "Our internal Nebula standard Gradle wrapper and plugin suite automatically enable the ArchRules runner on every project, and provides a custom reporter which sends the report data to our Internal Developer Portal on every main-branch CI build."
  5. Library author reads the dashboard, sees exactly which downstream repos still call deprecated APIs, decides when to remove.

"This way, library authors can easily see a report of all downstream consumers using their experimental, deprecated, or non-public APIs, giving them confidence to make 'breaking' changes, knowing that it will not actually break downstream consumers. If their changes are currently blocked by downstream usage, they can easily see exactly which projects are reporting those usages."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

Customization on the consumer side

"In a project running rules, you also have the ability to customize rule configurations using the archRules extension. For example, you can override a rule's priority level"

archRules {
    ruleClass("com.netflix.nebula.archrules.deprecation") {
        priority("HIGH")
    }
}

Plus "disabling running rules on certain source sets and configuring the failure threshold (i.e., high priority failures will cause the build to fail)." The shape: producer ships rules with default severity; consumer owns the policy.

Reporting

The Runner ships two built-in reports:

  • JSON file — collects output from all source sets within a project into one JSON aggregating violations.
  • Console — human-readable summary printed to build output; "failure details feature a detailed plain English description, along with a pointer to the exact line of code in violation."

For custom reporting "you can either use the JSON file, or create your own task that reads the binary files. Take a look at the source code for the ArchRules runner plugin's report tasks for an example of how to do this." Netflix internally ships a custom reporter sending data to its Internal Developer Portal.

Forward-looking work

Two named directions per the post:

  • Auto-remediation: "We will be exploring how to tie auto-remediation solutions into the ArchRules findings. ArchUnit currently provides very specific and detailed information about failures in reports, which makes a very strong input signal to an auto remediation tool. We will explore deterministic solutions such as OpenRewrite and non-deterministic solutions such as LLMs."
  • IDE inspections: "We also will investigate how to get ArchRule failure information surfaced in the IDE as inspections" — moving feedback from CI-blocking to author-time.

Seen in

What is not documented

  • Per-rule execution latency, plugin-evaluation overhead per build, dashboard-ingest throughput.
  • Time-to-deploy for new rule across the fleet (rule-authoring → publish → CI-rollout).
  • False-positive rate, fix-rate after detection.
  • Year-over-year trend on the 358 / 5,000 / 1M numbers.
  • Internal Developer Portal architecture beyond "a custom reporter which sends the report data."
  • Build-failure social dynamics — when a high-priority rule fires across N downstream repos, who pays cleanup cost?
  • Cost analysis vs alternatives (e.g. monorepo, custom AST tools).
Last updated · 542 distilled / 1,571 read