# ArchUnit: Java Architecture Rules as Unit Tests

> ArchUnit turns package, layer and slice constraints into plain JUnit assertions over imported bytecode. Here is how the fluent API works, where it stops being the right tool, and what to verify before adopting it.

**TNG/ArchUnit** — A Java architecture test library, to specify and assert architecture rules in plain Java

- Repository: https://github.com/TNG/ArchUnit
- Website: https://archunit.org
- Stars: 3,847 · Forks: 349
- Language: Java
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/tng-archunit

## What ArchUnit actually checks in a Java codebase

ArchUnit is a library for checking the architecture of Java code, per the README, which states that it can check dependencies between packages and classes, layers and slices, cyclic dependencies and more. The unit of work is not source text but bytecode: the README says ArchUnit analyzes given Java bytecode and imports all classes into a Java code structure. That distinction matters. A rule about which packages may call which other packages is evaluated against the compiled class graph, so the rule sees what the JVM sees, including synthetic and generated types that a source-level linter might skip.

The audience is teams that already write unit tests and want architecture constraints to fail the build the same way a broken assertion does. The README frames the main focus as automatically testing architecture and coding rules using any plain Java unit testing framework, so there is no separate runner to operate and no server to host. If your organization's answer to "who enforces the layering" is currently a Confluence page, ArchUnit is aimed squarely at that gap.

## How the importer and the fluent rule API fit together

The data flow has two stages. First, ClassFileImporter reads bytecode and produces a JavaClasses object, which is the in-memory model of everything you asked it to load. Second, an ArchRule is checked against that model. The README example shows exactly this split: importPackages("com.myapp") builds the structure, then rule.check(importedClasses) evaluates it.

Rules themselves are built through a fluent API, which the README points at with the classes() entry point and a GIF titled "ArchUnit Fluent API". The README does not enumerate the terminal operations in the text, so the exact vocabulary lives in the user guide at archunit.org and in the ArchUnit-Examples repository rather than in the README. What is visible from the repository layout is that the rule language, the importer and the domain model are separate modules inside the archunit/ directory, and that archunit-junit/ exists as a distinct integration.

One consequence of the two-stage design is that importing is not free. Every class you pull in becomes part of the model, and the README gives no guidance on narrowing the import beyond package names. On a large application, choosing the right import scope is a design decision you make, not something the library decides for you.

## Adding ArchUnit to a Maven or Gradle build

The README gives the dependency for both build systems, with the version pinned to 1.5.0. For Maven, the artifact is scoped to test, which is the intended placement since architecture rules run as tests.

```xml
<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit</artifactId>
    <version>1.5.0</version>
    <scope>test</scope>
</dependency>
```

For Gradle, the same coordinates go on the test implementation configuration.

```bash
testImplementation 'com.tngtech.archunit:archunit:1.5.0'
```

The README then shows a first test class. It imports the classes under com.myapp and calls check on a rule, with the rule body left as an exercise pointing to the next section.

```java
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

public class MyArchitectureTest {
    @Test
    public void some_architecture_rule() {
        JavaClasses importedClasses = new ClassFileImporter().importPackages("com.myapp");

        ArchRule rule = classes()... // see next section

        rule.check(importedClasses);
    }
}
```

What you should see is a normal test result: green when the imported classes satisfy the rule, a failure with the offending dependency when they do not. The README does not document the failure message format, so treat the first failing run as the moment you learn how much detail the report gives you. For the rule body itself, the README directs readers to the user guide at archunit.org and to the ArchUnit Examples repository for the current release.

## Where ArchUnit is the wrong tool

ArchUnit reasons about structure, not behavior. It cannot tell you whether a method computes the right answer, whether a transaction boundary is correct, or whether a service call is idempotent. If your failing test is about a race condition in an order pipeline, no rule in this library will express it.

There is a second, sharper limitation. The README describes importing classes into a Java code structure, which means the analysis target is bytecode on the classpath. The README does not document how the importer handles classes that fail to resolve, and it does not describe a rollback or incremental mode. That silence is worth taking seriously: a rule that depends on a class the importer cannot load is a rule whose result you should verify on your own build rather than assume. The README also does not document any caching of the imported model, so a test class that imports a large package tree pays the import cost on each run unless you structure the imports yourself.

Finally, ArchUnit is Java-specific. The related searches show people looking for archunit c#, archunit .net, archunit python, archunit kotlin and archunit for typescript. Kotlin compiles to JVM bytecode and so lands inside the same importer, but the README makes no claim about Kotlin support either way. For C#, .NET, Python or TypeScript, this project is not the answer and the README offers no port.

## ArchUnit against JDepend, Checkstyle and SonarQube

The closest conceptual neighbor in the search data is JDepend, which also analyzes Java package dependencies. The difference in approach is where the rule lives. JDepend computes metrics such as afferent and efferent coupling and cycles, and reports them; the judgement about whether a number is acceptable sits outside the tool. ArchUnit inverts that. You write the constraint as an assertion in Java, and the test passes or fails. A cycle is not a metric to review, it is a red build.

Checkstyle and SonarQube operate on a different axis. Checkstyle is a source-level style and convention checker, and SonarQube is a platform that aggregates many kinds of analysis across a codebase. Neither is built around the idea that a team member writes an executable assertion about package layering in the same file as the rest of the test suite. That is ArchUnit's specific bet: architecture rules belong in version control next to the code they constrain, reviewed in the same pull request.

The trade-off is real. A metric dashboard shows you drift over time without anyone writing a rule; ArchUnit shows you nothing until someone encodes the expectation. Teams that want a number to watch should stay with the metric tools. Teams that want a merge blocked should look here.

## Versions, licence and the cost of staying current

ArchUnit is published under the Apache License 2.0, per the README, which points to the canonical text at apache.org. The README also states that the project redistributes third party libraries: ASM under the BSD licence and Google Guava under Apache License 2.0, with all licence texts collected in the licenses/ directory of the repository. If your organization runs automated licence scanning, those two redistributed dependencies are the ones to confirm against your policy. Nothing here is legal advice; the licenses/ folder is the authoritative place to look.

On maintenance, the repository is not archived, and the last push was on 2026-09-23. The release cadence visible in the tags runs from v1.3.2 in May 2025 through v1.4.2 in April 2026 to v1.5.0 in August 2026. That is roughly one minor release per year with patch releases between, so the upgrade cost is not a continuous tax. The practical work of upgrading is usually the version string in your build file plus a check that the rule syntax you rely on has not changed, since the README does not publish a migration guide. Note also that the JUnit integration is a separate artifact under archunit-junit/, so pinning the core library and the integration to the same version is a step the README leaves to you.

## Conclusion

Adopt ArchUnit if you already run JUnit and want dependency rules enforced in CI rather than in a wiki page. Skip it if your problem is code style, coverage or data flow, since it only reasons about imported bytecode structure. Before rolling it out across a monorepo, check how many classes new ClassFileImporter().importPackages pulls into memory on your largest module and whether your build already resolves the archunit-junit5 integration, because that determines whether rules run per test class or as a single frozen suite.

## FAQ

### What is ArchUnit in Java?

It is a library for checking the architecture of Java code, according to the README, by analyzing Java bytecode and importing all classes into a Java code structure. Its stated focus is automatically testing architecture and coding rules using any plain Java unit testing framework.

### How do I use ArchUnit?

Add the com.tngtech.archunit:archunit dependency to your test scope, import your packages with new ClassFileImporter().importPackages("com.myapp"), build an ArchRule through the fluent API, and call rule.check(importedClasses) inside a test. The README points to archunit.org and the ArchUnit Examples repository for the rule syntax.

### What is an ArchUnit test?

It is an ordinary unit test whose assertion is an architecture rule rather than a method result. The README's example imports classes from a package and checks a rule against them, so the test fails when the dependencies in the imported bytecode violate the rule.

### Is ArchUnit an alternative to JUnit?

No. The README states that ArchUnit tests architecture and coding rules using any plain Java unit testing framework, so it runs inside JUnit rather than replacing it. The example test uses a JUnit @Test annotation.

### How does ArchUnit compare with Checkstyle?

They check different things. ArchUnit checks dependencies between packages and classes, layers and slices, and cyclic dependencies by analyzing bytecode, while Checkstyle is not discussed in the README at all. The overlap is limited to the fact that both can fail a build.

## Sources

- [License: Apache-2.0](https://github.com/TNG/ArchUnit/blob/main/LICENSE)
- [Project website](https://archunit.org)
- [README](https://github.com/TNG/ArchUnit/blob/main/README.md)
- [Releases](https://github.com/TNG/ArchUnit/releases)
- [TNG/ArchUnit on GitHub](https://github.com/TNG/ArchUnit)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tng-archunit
