Skip to content

SYSTEM Cited by 1 source

ArchUnit

ArchUnit (archunit.org) is a popular OSS Java library for enforcing architectural code rules as part of a JUnit suite. It analyzes compiled JVM bytecode via ASM and exposes a fluent type-safe builder API for rule authoring. As of the 2026-05-08 Netflix TechBlog post that triggered this wiki page, ArchUnit has "3.5k stars, 84 contributors" and is "used internally by Gradle, Spring, and is provided as part of the Spring Modulith platform."

This page is the canonical wiki home for ArchUnit, primarily documenting its role as the primitive on top of which Netflix's Nebula ArchRules is built.

Three distinctive features (per Netflix post)

"The rules engine, which is built directly on top of ASM, can be used for a wide variety of use cases. It is powerful enough to be a general purpose static analysis tool with the following distinctive features:

1. Works cross-language (JVM), because it uses ASM/bytecode, not AST parsing.

2. Exposes a builder API pattern that makes it easy to write rules

3. Also has a lower level API ideal for writing more complex custom rules."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

These three properties — language-agnostic via bytecode, fluent builder API, classpath-graph navigation — are the load-bearing design choices distinguishing ArchUnit from AST-based JVM static analyzers like PMD or Spotbugs.

Builder API rule example

The Netflix post's verbatim rule example for "DateTimes are not instantiated without an explicit zone":

ArchRuleDefinition.priority(Priority.MEDIUM)
.noClasses()
.should()
.callConstructorWhere(
    // constructor does not have a zone arguement
    target(doesNot(have(rawParameterTypes(DateTimeZone.class))))
   // constructor is for DateTime
        .and(targetOwner(assignableTo(DateTime.class)))
)

Properties of this style:

  • Type-safe — Java compiler validates the rule shape.
  • IDE-supported — autocomplete on target(...), targetOwner(...), doesNot(...), etc.
  • Unit-testable in-process"ArchUnit has a method to pass a rule object and class references to evaluate the rule against those classes" — no separate analyzer process needs to spin up.
  • Composable — predicates compose with .and(...) / .or(...) / .negate().

Bytecode (ASM) over AST

ArchUnit's choice of ASM for class-graph construction (vs source-AST tools like PMD) is the architectural move that produces three downstream properties (per the Netflix post):

  1. Language-agnostic across JVM languages"Rules that need to support multiple JVM languages, such as Kotlin or Scala, often need to be rewritten for each language. … ArchUnit uses ASM to analyze actual compiled bytecode, which means it doesn't matter how that code was produced. What is analyzed is the actual code that will be run."
  2. Syntactic-sugar-immune"It also allows code which should be found to be hidden under syntactic sugar not anticipated by the rule author." Kotlin extension functions, Scala implicits, Java records, lombok-generated code all desugar to bytecode the rule sees.
  3. Code-generator-aware — annotation processors, Kotlin Symbol Processing (KSP), kapt, Lombok, etc. all generate bytecode that ArchUnit analyzes — so generated code is checked, not just hand-authored source.

See concepts/bytecode-vs-ast-static-analysis for the canonical wiki framing.

Class-graph retention

"Because ArchUnit processes the entire classpath with ASM, it retains a graph of the class data, allowing rules to easily traverse class relationships and call sites. This allows rules to have much more context about the code it is evaluating."sources/2026-05-08-netflix-scaling-archunit-with-nebula-archrules

This enables cross-class rules that per-file AST tools can't express:

  • "No class outside this package may depend on a @Deprecated class inside this package."
  • "All implementations of Service must reside in com.example.impl."
  • "No layer-N class may call into a layer-(N+1) class."
  • "Every @Entity must be referenced from exactly one Repository."

These rules require call-site enumeration + class-hierarchy walking — operations that are natural on a built classpath graph and awkward at best on a per-file AST.

Standard usage shape: JUnit suite

ArchUnit is "designed to be used as part of a JUnit suite in a single repository" (per the Netflix post — this is named as the limitation that motivates Nebula ArchRules). A typical project includes ArchUnit as a test dependency, writes rule definitions in src/test/java, and JUnit asserts the rules during normal test execution.

This works for a single repo but doesn't address the polyrepo problem of sharing rules across thousands of repos — which is where Nebula ArchRules picks up.

Lower-level API

The Netflix post mentions "Also has a lower level API ideal for writing more complex custom rules" without expanding. ArchUnit exposes its JavaClasses model directly so rule authors can walk classes, methods, fields, dependencies, and annotations imperatively when the fluent DSL doesn't fit.

Seen in

What is not documented

  • The post does not deeply document ArchUnit's lower-level API beyond mentioning it exists.
  • ArchUnit's caching / incremental-build behavior, performance characteristics, and memory footprint at large class-graph scale are not discussed.
  • The OSS contributor / governance model is not discussed beyond the "3.5k stars, 84 contributors" number as of early 2026.
Last updated · 542 distilled / 1,571 read