Backend Development 9 min read

Testing Java Architecture with ArchUnit: A Comprehensive Guide

This article explains how Java developers can use ArchUnit to automatically import and analyze compiled classes, define custom architectural rules, enforce package naming conventions, prevent cyclic dependencies, and integrate the library with Maven or Gradle builds to maintain a clean, layered backend architecture.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
Testing Java Architecture with ArchUnit: A Comprehensive Guide

Origin of ArchUnit

In 2018, the German IT consultancy TNG Technology Consulting organized a programming retreat where developers Peter Gafert, Mariusz Smykula and others created ArchUnit to address the difficulty of maintaining architectural consistency in large Java systems. After internal validation, TNG open‑sourced ArchUnit in May 2018 under the Apache 2.0 license; since then more than 75 contributors have submitted over 300 pull requests.

Core Concepts

Import and analyze Java classes : Dynamically load compiled bytecode into memory using byte‑code parsing techniques, allowing deep inspection of class structure without prior compilation.

Detect and report violations : After importing, write rules with Java 8 lambdas and custom assertions; any class, method or relationship that breaks a rule throws an architecture‑violation exception with a clear message.

Getting Started with ArchUnit

Adding the Dependency

For Maven projects:

<!-- ArchUnit dependency -->
com.tngtech.archunit
archunit
0.22.0
test

For Gradle projects:

// ArchUnit for testing
testImplementation 'com.tngtech.archunit:archunit:0.22.0'

Creating a Test Class

Create a test class annotated with @AnalyzeClasses and specify the package to be examined:

@AnalyzeClasses(packages = "com.myapp.layers")
public class MyArchitectureTests {

}

When the test runs, ArchUnit checks the classes in com.myapp.layers and all its sub‑packages.

ArchUnit vs. Other Tools

Library

Pros

Cons

JArch

Simple annotations

Limited checks, poor error reporting

SQA‑Mashup

Integrates with SonarQube

Complex setup, focuses on metrics rather than structure

ArchUnit

Fluent API, customizable rules, readable violation messages

Steeper learning curve for the syntax

Enforcing Package‑Naming Conventions

Most projects follow a convention where controller classes end with “Controller” and reside in a controller package. ArchUnit can enforce this with a simple rule:

@ArchTest
static final ArchRule controller_suffix = classes()
    .that().resideInAPackage("..controller..")
    .should().haveSimpleNameEndingWith("Controller");

If a controller does not end with “Controller”, a readable violation is thrown. A similar rule can enforce that repository classes live in a repository package:

@ArchTest
static final ArchRule repo_location = classes()
    .that().areAnnotatedWith(Repository.class)
    .should().resideInAPackage("..repository..");

Preventing Cyclic Dependencies

Even small cycles increase coupling. ArchUnit makes it easy to forbid them:

@ArchTest
static final ArchRule no_cycles = slices()
    .matching("com.myapp.(*)..")
    .should().beFreeOfCycles();

The rule blocks any cyclic calls between sub‑packages of com.myapp and fails the build when a cycle is introduced.

Enforcing Layered Architecture

A strict layered architecture allows only one‑way dependencies (e.g., Controllers → Services → DAOs). The following rule defines the layers and permissible accesses:

@ArchTest
static final ArchRule layer_dependencies = layeredArchitecture()
    .layer("Controllers").definedBy("com.myapp.ui")
    .layer("Services").definedBy("com.myapp.services")
    .layer("DAOS").definedBy("com.myapp.dataaccess")
    .whereLayer("Controllers").mayOnlyBeAccessedByLayers("Services")
    .whereLayer("Services").mayOnlyBeAccessedByLayers("Controllers", "DAOS")
    .whereLayer("DAOS").mayNotBeAccessedByAnyLayer();

Now direct calls from Controllers to DAOs are prohibited.

Advanced Usage

Custom Rules

Define your own annotation to mark service classes:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service { }

Then write a rule that ensures such classes reside in a service package:

@ArchTest
static final ArchRule services_isolated = classes()
    .that().areAnnotatedWith(Service.class)
    .should().resideInAPackage("..service..");

Integration with Build Tools

Integrate ArchUnit into CI pipelines so that architecture violations fail the build. The Maven plugin can be configured as follows:

<plugin>
  <artifactId>archunit-maven-plugin</artifactId>
  <version>0.22.0</version>
  <configuration>
    <rules>
      <rule>src/test/java/MyRules.java</rule>
    </rules>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>verify</goal>
      </goals>
      <phase>test</phase>
    </execution>
  </executions>
</plugin>

When a violation is detected, the build fails.

Implementing Hexagonal Architecture

ArchUnit can also help enforce a ports‑and‑adapters (hexagonal) architecture:

@ArchTest
static final ArchRule ports_and_adapters = layeredArchitecture()
    .layer("Ports").definedBy("..api..")
    .layer("Adapters").definedBy("..adapters..")
    .layer("Domain").definedBy("..domain..")
    .whereLayer("Adapters").mayOnlyBeAccessedByLayers("Ports")
    .whereLayer("Ports").mayOnlyBeAccessedByLayers("Adapters", "Domain")
    .whereLayer("Domain").mayOnlyBeAccessedByLayers("Ports");

This separation allows UI and adapters to evolve independently while keeping the domain logic pure.

Start Testing Your Architecture

The article covered four key aspects of using ArchUnit to manage Java architecture complexity: importing and inspecting packaged code, expressing structural concepts clearly, preventing architectural decay over time, and supporting incremental improvement.

When you start a new Java project, consider adding ArchUnit tests early; a small upfront investment pays off by keeping the system clean and maintainable for the long term.

backendJavaGradleMavenarchitecture testingArchUnit
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.