In agile software development, the quality of the architecture counts. It influences maintainability, expandability and performance in the long term. But how can you ensure that all teams adhere to architecture specifications? The solution: Automated architecture tests with ArchUnit.
Why is architectural quality crucial for you?
ISO 25010 specifies many quality characteristics - including changeability, reusability and modularity. As soon as your code violates defined rules, it becomes difficult to understand and hardly maintainable.
Typical challenges:
- Misinterpretation of architecture diagrams during implementation
- Unknown or manually documented rules
- Inconsistent implementations across teams or microservices
The consequences of this are deviations between the planned and actual architecture as well as poor maintainability and expandability.
ArchUnit: Test architecture
ArchUnit is an open source framework for Java (forks for .NET, TypeScript and Python are also available). It allows you to formulate architecture rules directly in the code - in the form of unit tests.
Your advantages:
- Legible rules through Fluent API ("English sentences" enable even the PO, architect or consultant to understand and pre-formulate the rules)
- Automated Executable through CI/CD
- Architectural rules versioned Maintain via Git
Typical ArchUnit rules - with examples
ArchUnit offers a wide range of options for automated checking of architectural specifications in the code. The following examples show some typical use cases - representative of many others that can be individually supplemented and used depending on the project.
Example 1: Layer checks
The layer checks ensure that the accesses between the layers are correctly maintained.

Code example
layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Persistence").definedBy("..persistence..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")
Example 2: Cycle checks
Cycle checks ensure that modules are clearly decoupled from each other and structured without cyclical dependencies.

Code example:
slices().matching("com.myapp.(*)..").should().beFreeOfCycles()
Example 3: Inheritance checks
Inheritance checks ensure that classes that implement certain interfaces adhere to consistent and compliant naming conventions.

Code example:
classes().that().implement(Connection.class)
.should().haveSimpleNameEndingWith("Connection")
Example 4: Class Dependency Checks
The class dependency checks ensure that classes are only dependent on the permitted other classes and that unwanted dependencies are avoided.

Code example:
classes().that().haveNameMatching(".*Bar")
.should().onlyHaveDependentClassesThat().haveSimpleName("Bar")
Practical tips for your project
These principles have proven themselves in our experience:
- Define rules early on
- Use base classes for reusable structures such as architecture rules
- Existing Referencing PlantUML diagrams to compare the planned with the actual implementation
- Allow specific exceptions via whitelisting
- Quick start with GeneralCodingRules
Conclusion: Architecture tests with ArchUnit are worthwhile
ArchUnit is lightweight, CI/CD-capable and individually expandable. It can be quickly integrated into existing pipelines - and ensures sustainable architectural quality.
ArchUnit is a real benefit, especially for complex projects. Instead of having to keep manually documented rules in your head, the defined rules are automatically checked in the code and taken into account during implementation.1
This article was written jointly by Linda and Marcel.

Marcel Sommer
You can find more blog posts by Marcel here.



