Enforce architecture boundaries against compiled Java code. ArchUnitJava reads class directories and JARs without loading target classes, builds an immutable dependency model, evaluates deterministic policies, and produces evidence for JUnit and CI.
Public beta:
io.github.tristankruse:archunitjava:0.1.0is published on Maven Central. Its signed tag, Central validation, checksums, signatures, and publication record are documented in the release assessment. The pre-1.0 API remains provisional and may change between minor versions.
Inspired by the established ArchUnit project, but independently implemented and not affiliated with ArchUnit. This is not a drop-in replacement.
Quickstart · Use cases · Capabilities · Reports · Example repository · User guide · CLI reference · Documentation · FAQ · Contributing · Support · Limitations · Adoption metrics
The Java packaging terms are easy to mix up: Maven Central is the public package registry,
Central Portal is Sonatype's publisher UI and API, and Maven and Gradle are dependency
tools that download packages from registries. Consumers do not need a Central Portal account.
Maven uses Central by default; Gradle consumers only need mavenCentral().
<dependency>
<groupId>io.github.tristankruse</groupId>
<artifactId>archunitjava</artifactId>
<version>0.1.0</version>
<scope>test</scope>
</dependency>Production application code does not need an ArchUnitJava dependency.
For Gradle:
repositories {
mavenCentral()
}
dependencies {
testImplementation("io.github.tristankruse:archunitjava:0.1.0")
}Create archunitjava.properties in the analyzed project:
schema=archunitjava.cli.v1
inputs=target/classes
rules=api-boundary
emptySelection=fail
allowIncompleteAnalysis=false
resultFormat=json
graphFormat=mermaid
graphDomain=types
rule.api-boundary.domain=types
rule.api-boundary.mode=no
rule.api-boundary.origins=glob:com.example.api.**
rule.api-boundary.targets=glob:com.example.infrastructure.**
rule.api-boundary.self=ignore
rule.api-boundary.external=ignore
rule.api-boundary.displayName=API must not bypass the application layer
rule.api-boundary.rationale=Keep concrete adapters behind application ports
rule.api-boundary.tags=api,boundary
rule.api-boundary.severity=errorRules use qualified Java binary names. CLI patterns are either exact: or bounded glob: values;
arbitrary regular expressions and executable configuration hooks are intentionally unsupported.
import static org.junit.jupiter.api.Assertions.assertEquals;
import dev.archunitjava.cli.CliExitCode;
import dev.archunitjava.cli.CliRunner;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
final class ArchitectureTest {
@Test
void architecturePoliciesPass() {
Path root = Path.of("").toAbsolutePath().normalize();
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
int exit = new CliRunner().run(new String[] {
"check",
"--config", root.resolve("archunitjava.properties").toString(),
"--root", root.toString(),
"--result-format", "json"
}, output, error);
assertEquals(CliExitCode.SUCCESS.code(), exit, error.toString());
}
}Run it with the rest of the suite:
./mvnw testThis same workflow is exercised by the independent RAG consumer on Linux and Windows.
compiled classes and JARs
│
▼
EXTRACT Parse bytecode as untrusted data; never load target classes
│
▼
PROJECT Build stable type, package, member, slice, layer, and module views
│
▼
ASSERT Evaluate immutable architecture rules with explicit completeness
│
▼
REPORT Preserve subjects, dependency kinds, bytecode locations, and diagnostics
Rule construction performs no I/O. Empty selections fail by default. A policy disagreement, incomplete analysis, invalid configuration, and internal analysis failure are separate outcomes; they are not collapsed into one generic exception or exit code.
Express boundaries such as:
- API code must not depend directly on persistence adapters.
- Domain code must remain independent of infrastructure frameworks.
- Application services may depend only on approved domain ports.
- One JPMS module must not read or export another module.
The properties-driven CLI currently exposes dependency policies over types and packages. The Java API additionally contains rules for members, inheritance, annotations, cycles, slices, layers, modules, public interfaces, reachability, and exhaustive policies.
The graph model retains concrete origin and target identifiers plus evidence such as the dependency kind, owning member, bytecode offset, source file, and line number when available. Cycle and slice rules therefore report why a boundary failed instead of returning only a boolean.
ArchitectureAssertions.assertPasses(...) translates structured results into test-runner failures.
ArchitectureTestCases supports dynamic tests, and the optional JUnit Platform engine discovers
explicit @ArchitectureTest methods without scanning or loading unrelated target classes.
The CLI has four commands:
| Command | Purpose |
|---|---|
validate-config |
Validate the bounded configuration and approved paths |
explain |
Show the normalized rule definitions without analyzing bytecode |
check |
Analyze inputs and evaluate all configured policies |
graph |
Render a type or package dependency graph |
Stable exit codes distinguish success (0), usage errors (2), invalid configuration (3),
analysis failure (4), and policy violations (5).
| Area | Implemented surface |
|---|---|
| Inputs | Class directories, JARs, classpaths, multi-release JARs, Maven/Gradle project discovery |
| Java model | Types, records, sealed types, nested types, packages, members, generics, annotations, inheritance, JPMS |
| Dependencies | Declarations, signatures, annotations, exceptions, calls, fields, constants, method handles, dynamic constants, lambdas, method references |
| Selectors | Types, packages, members, semantic properties, glob/exact matching, composition |
| Rules | Dependencies, names, locations, inheritance, annotations, member access, cycles, slices, layers, modules, dead types, public interfaces, presets |
| Test integration | Framework-neutral assertions, JUnit Jupiter usage, dynamic cases, JUnit Platform engine |
| Build integration | CLI configuration plus Maven and Gradle invocation bridges |
| Architecture artifacts | Graph snapshots, PlantUML contracts, reviewed baselines, result exports |
| Metrics | Source, cohesion, and dependency metrics with deterministic snapshots |
| Operational controls | Bounded diagnostics, import filters, .archignore, cache keys, resource and path limits |
The generated API reference lists every public
package and type. The user guide maps every important feature area to its
normal workflow and API entry point; the CLI reference documents every
configuration key, command, format, and exit code. The internal architecture and ownership rules
are described in docs/ARCHITECTURE.md.
Rule results can be rendered as:
- console text;
- canonical JSON;
- SARIF for code-scanning systems; or
- JUnit XML for CI test reporting.
Dependency graphs can be rendered as:
- DOT;
- Mermaid;
- D2;
- CSV;
- canonical JSON; or
- self-contained HTML.
For example, switch a configured graph to Mermaid without changing the policy file:
int exit = new CliRunner().run(new String[] {
"graph",
"--config", configuration.toString(),
"--root", approvedRoot.toString(),
"--graph-format", "mermaid"
}, output, error);Renderers escape untrusted labels and impose deterministic order and size limits. CSV is
spreadsheet-safe by default. The explicitly named renderMachineReadable Java API preserves values
without formula neutralization and must be treated as data rather than opened directly in a
spreadsheet. See the threat model.
The ArchUnitJava RAG test repository is a normal, independently versioned Maven consumer. It models a retrieval-augmented-generation application with API, application, domain, infrastructure, and bootstrap packages.
Its five tests prove:
| Contract | Expected outcome |
|---|---|
| RAG application behavior | Pass |
| Domain and application isolation | Pass |
| API directly reaches an infrastructure adapter | Detected as a policy violation |
| Infrastructure imports an API DTO | Detected as a policy violation |
| Mermaid graph and strict configuration | Generated and validated |
The two bad dependencies are deliberate fixture data. Tests assert their structured failures, so the overall Maven build remains green. Both repositories verify the integration on Ubuntu and Windows using GitHub's ordinary read-only public checkout; neither workflow stores an access token.
The smaller examples/basic consumer remains in this repository as a fast,
mandatory release-candidate smoke test.
ArchUnitJava treats target repositories and bytecode as untrusted data:
- target classes are never loaded, initialized, or reflected over;
- target builds, plugins, and annotation processors are never executed by analysis;
- configuration cannot name executable factories or commands;
- approved roots contain configuration, input, cache, diagram, and output paths;
- archive traversal and decompression have explicit limits;
- parser and resolution diagnostics are bounded; and
- HTML, graph, JSON, XML, and SARIF output escapes target-controlled text.
Static bytecode analysis cannot see dependencies introduced only through reflection, native code,
runtime generation, service lookup, dependency injection configuration, or dynamically assembled
strings. The lower-level JavaPattern.regex API accepts a bounded safe subset; unrestricted JDK
regular expressions require the explicitly named trustedRegex method and trusted in-process
policy. See docs/THREAT_MODEL.md for the full boundary and residual risks.
- JDK 25 is required to build and run the current library.
- Maven 3.9.x is supplied through the checked-in wrapper.
javacbytecode from Java 8 through Java 25 (class-file majors 52–69) is covered by the extraction corpus.- Later bytecode is unsupported until explicitly tested.
- Older or non-
javacbytecode may parse, but is outside the release claim. - The library JAR exposes the automatic module name
dev.archunitjava; it is not yet a fully modular JAR.
Read the complete compatibility contract.
Linux and macOS:
./mvnw verifyWindows:
.\mvnw.cmd verifyCI runs the full suite on Ubuntu and Windows with JDK 25. Separate jobs verify:
- the independent RAG consumer;
- reproducible primary artifacts;
- source and Javadoc JARs;
- package contents and automatic-module metadata;
- a reviewed SpotBugs baseline that rejects new finding categories and mutable representation exposure;
- Central bundle construction, signing configuration, and local-only deployment; and
- the generated GitHub Pages/Javadoc site.
Useful technical guides:
- User guide and feature map
- CLI configuration reference
- Architecture
- Compatibility
- Migration and staged adoption
- Performance methodology
- Release process and readiness
- Threat model
- Research and product decisions
Contributions are welcome. Read CONTRIBUTING.md before changing public semantics, schemas, the JDK floor, Maven coordinates, or the bytecode backend. Every behavioral change should include a focused regression test, and packaging or integration changes should also be verified against the independent RAG consumer.
Please report vulnerabilities through GitHub's private security-advisory form, not a public issue. The supported scope and reporting guidance are in SECURITY.md.
The original ArchUnit is mature, widely used, and is generally the correct choice for production Java architecture testing today.
ArchUnitJava is a public beta that exists to explore a consistent ArchUnitEverything product family, a bytecode-as-untrusted-data security boundary, explicit completeness, deterministic evidence, and cross-language architecture-policy concepts. Evaluate this release when those goals are relevant and you are comfortable with a provisional pre-1.0 API. Do not migrate a production system on the assumption of ArchUnit compatibility.
- Version
0.1.0is a public beta; the API remains provisional before 1.0. - The CLI intentionally exposes only a subset of the lower-level Java rule surface.
- JDK 25 is currently required at runtime because the importer uses
java.lang.classfile. - Dynamic runtime dependencies are invisible to static bytecode analysis.
- API contracts remain provisional before 1.0 and are not compatible with ArchUnit's API.
- Performance data is regression evidence, not an absolute scalability claim.
These limitations are tracked in the release assessment rather than hidden behind a stability claim.
Maven Central Publisher Insights is enabled for
the io.github.tristankruse namespace. It tracks rolling three-month download totals, distinct
sources, and identified companies. The linked
Scarf integration adds
per-package, version, date, geography, and adoption breakdowns after Maven package discovery.
Initial Scarf discovery can take a few days, and new Maven Central activity normally appears about one week later. Counts represent artifact fetches rather than unique users: repeated builds, CI, repository mirrors, and dependency-cache misses can all contribute. Repository views, clones, and stars remain separate GitHub metrics. ArchUnitJava does not add runtime telemetry to applications or analyzed code.
Is Maven Central the Java equivalent of npm, PyPI, or RubyGems?
Yes. Maven Central is the public registry. Maven and Gradle are the usual clients; Central Portal is used only by maintainers to stage and publish releases.
Will an application need JDK 25 even if its own bytecode targets an older Java version?
For version 0.1.0, yes: ArchUnitJava itself runs on JDK 25 because it uses the JDK
class-file API. It can analyze javac bytecode produced for Java 8 through Java 25.
Does analysis execute application classes or the target project's build?
No. It parses class directories and JARs as untrusted data. It does not load target classes, invoke their initializers, or run target build plugins and annotation processors.
Does it work only with JUnit?
No. The core result and assertion APIs are framework-neutral. JUnit Jupiter helpers and an optional JUnit Platform engine are included because JUnit is the normal Java test workflow.
Is this a replacement for the established ArchUnit library?
Not currently. ArchUnit is the mature default for production Java systems. ArchUnitJava is an independent, provisional implementation exploring the cross-language ArchUnitEverything model and a stricter bytecode-as-untrusted-data boundary.
ArchUnitJava is maintained by TristanKruse. To participate:
- read the support policy for usage questions and bug reports;
- join GitHub Discussions for usage and design questions;
- open an issue for a reproducible bug or feature;
- review existing issues; or
- contribute code or documentation through a pull request.
Participation is governed by the code of conduct. Report vulnerabilities privately as described in SECURITY.md.
The implementations share a product idea, not source compatibility: architecture decisions should be executable, evidence-rich, deterministic, and natural to run in each language's normal test workflow.
The current development version is licensed under the MIT License. The published
0.1.0 release remains under
the Apache License 2.0; released artifacts keep the license under which they were published.
