Why Architecture Testing Matters in a Polyrepo World

At Netflix, the JVM ecosystem spans tens of thousands of Java repositories. Without a centralized way to enforce architectural rules, technical debt accumulates silently — especially around deprecated or internal APIs. The challenge: how do you prevent downstream consumers from using APIs that library authors intend to remove?

Traditional static analysis tools (PMD, SpotBugs) fall short for custom rule authorship. Their rule languages (XPath, XML) are opaque, hard to test, and language-specific. ArchUnit, built on ASM bytecode analysis, offers a fluent Java API that is type-safe and easy to unit test. But ArchUnit alone is designed for single-repository use. Nebula ArchRules bridges this gap, enabling organizations to define, publish, and run ArchUnit rules across hundreds or thousands of Gradle projects.

Case Study: API Lifecycle Management at Scale

After an incident caused by a library removing deprecated code (deprecated for years, but still used downstream), Netflix’s Java Platform team needed a systematic solution. They introduced a suite of API lifecycle annotations:

  • @Deprecated (standard Java)
  • @Public — intended for downstream use
  • @Experimental — unstable API
  • Unannotated = internal

Library authors could mark their APIs, but how would they know which downstream projects used them incorrectly? Enter Nebula ArchRules.

How Nebula ArchRules Works

1. Writing Shareable Rules

The ArchRules Library Plugin adds a separate archRules source set. You implement the ArchRulesService interface, returning a Map<String, ArchRule>. Example:

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;
    }
}

Rules are packaged into a separate JAR with the arch-rules classifier, published alongside the main library. Two flavors exist:

  • Standalone Rule Libraries: contain only rules (e.g., “don’t use @Deprecated APIs”). Useful for OSS or core Java APIs.
  • Bundled Rule Libraries: include both main code and rules scoped to that library’s package. Automatically applied when downstream projects depend on the library.

2. Running Rules Across Repositories

The ArchRules Runner Plugin automatically discovers and evaluates rules. For standalone rules, add them to the archRules configuration:

dependencies {
    archRules("com.netflix.nebula:nebula-archrules-deprecation:1.0.0")
}

Bundled rules are evaluated automatically. The plugin creates a separate classpath for each source set, combining the runtime classpath with the arch-rules variant. A Gradle work action runs ArchUnit with classpath isolation, and violations are serialized to a binary file.

3. Customization & Reporting

You can override rule priorities, disable rules on certain source sets, or set failure thresholds:

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

Two built-in reports: JSON (machine-readable) and console (human-readable with line-level violation pointers). Custom reports can read the binary files directly.

Netflix's polyrepo architecture with thousands of Java microservices managed by Nebula Gradle plugins Programming Illustration

Real-World Impact: 358 Rules, 5,000+ Repos, 1M Issues Detected

Netflix is now running 358 rules across over 5,000 repositories, detecting nearly 1 million issues. About 1,000 of these are high-priority violations. The data flows into Netflix’s Internal Developer Portal, giving library authors visibility into exactly which downstream consumers use deprecated or experimental APIs.

Example: Detecting Deprecated API Usage

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");

This rule catches both direct dependency on deprecated classes and access to deprecated methods or fields — all scoped to a specific library package.

Limitations & Cautions

  • Gradle-only: The plugin relies on Gradle Module Metadata and custom configurations. Maven or other build systems are not supported.
  • Bytecode ≠ Source: ArchUnit analyzes compiled bytecode, which means it can miss code hidden behind syntactic sugar (e.g., Kotlin inline functions that expand to non-annotated bytecode).
  • Performance: Running 358 rules across thousands of repos requires careful CI pipeline design. Netflix runs this on every main-branch build, which adds overhead.
  • Rule Maintenance: Rules must be kept up-to-date with library changes. A stale rule that flags a no-longer-relevant API creates noise.

Next Steps & Future Directions

Netflix is exploring auto-remediation by pairing ArchUnit’s detailed failure reports with tools like OpenRewrite (deterministic) and LLMs (non-deterministic). The goal: automatically fix violations when possible. They also plan to surface ArchRule violations as IDE inspections.

If you’re building a polyrepo JVM organization, start small: write a few bundled rules for your core libraries, enable the runner in CI, and iterate. The OSS rule libraries (nullability, Gradle best practices, security) are a great starting point.

For more on responsible use of AI in coding, check out our guide on leveraging AI coding agents responsibly. And if you’re evaluating AI tools for your workflow, read Beyond the Hype: A Responsible Developer’s Guide to AI Coding Tools.

ArchUnit bytecode analysis engine detecting deprecated API usage across JVM projects Developer Related Image

Key Takeaways

AspectArchUnit + Nebula ArchRulesTraditional Static Analysis (PMD/SpotBugs)
Rule AuthorshipFluent Java API, type-safe, unit-testableXPath/XML strings, no IDE support
Language SupportBytecode-level (any JVM language)AST-based (language-specific)
SharingGradle Module Metadata + arch-rules variantPlugin ecosystem, no standardized sharing
Scalability5,000+ repos, 358 rulesTypically single-repo
CustomizationPriority overrides, source set exclusionsLimited to plugin configuration

Conclusion

Nebula ArchRules transforms ArchUnit from a single-repo testing library into a fleet-wide architecture enforcement platform. By combining the ease of rule authorship with automated discovery and reporting, Netflix has reduced the risk of breaking changes and made technical debt visible at scale. The approach is pragmatic: start with a few rules, iterate based on real incidents, and let the data guide your priorities.

This article is based on the original Netflix Tech Blog post by John Burns and Emily Yuan. For the full technical details, refer to the source article.

ArchRules violation report showing high-priority technical debt in a large-scale Java codebase Technical Structure Concept

This content was drafted using AI tools based on reliable sources, and has been reviewed by our editorial team before publication. It is not intended to replace professional advice.