Files
JUnit_Tutorials/archunit

ArchUnit for Spring Boot — Enforcing Layered Architecture as a Unit Test

Companion module for ArchUnit for Spring Boot: Enforcing Layered Architecture as a Unit Test on ankurm.com. Every code sample and every console transcript quoted in that post comes from the files in this directory — nothing was hand-typed into the article.

Versions this was built and run against

Component Version Notes
ArchUnit 1.5.1 current GA per maven-metadata.xml on Maven Central at the time of writing
JUnit Jupiter / Platform 6.1.3 via archunit-junit5, which brings its own JUnit 5 extension
Spring Framework 7.0.9 spring-context + spring-webmvc only — no spring-boot-starter-web, no embedded server, no ApplicationContext anywhere in this module
JDK 25 (Temurin, LTS) build and run; maven.compiler.release is set to 17
Maven 3.9.11
Maven Surefire Plugin 3.5.2

This module deliberately does not depend on any spring-boot-starter-* artifact. ArchUnit reads compiled .class files directly — it has never needed a running Spring context, and the whole point of the first section of the post is that none of these tests say @SpringBootTest.

Quickstart

mvn test                              # all four rules, no application ever starts, ~4s total
mvn test -Dtest=LayeredArchitectureTest   # just the three structural rules
mvn test -Dtest=FreezingArchRuleTest      # just the frozen-violation rule

Gradle users: there is no build.gradle.kts in this module (unlike this repo's other modules) because the whole demonstration is Maven-plugin-agnostic — archunit-junit5 plugs into the JUnit 5 engine the same way under either build tool, and the commands above are the part that matters.

Source files

File Demonstrates
OrderRepository.java / InMemoryOrderRepository.java the repository layer — a @Repository with no framework beyond the stereotype annotation itself
OrderStatusService.java the service layer — constructor injection only, which is what makes noFieldInjection meaningful rather than cosmetic
OrderStatusController.java the web layer — depends on the service layer only
LegacyOrderExporter.java a deliberately-kept pre-existing throws Exception violation — the thing FreezingArchRuleTest freezes rather than fixes
LayeredArchitectureTest.java layeredArchitecture(), noFields().should().beAnnotatedWith(Autowired.class), and slices().should().beFreeOfCycles() — three independent structural rules, all checked without starting anything
FreezingArchRuleTest.java FreezingArchRule.freeze(...) wrapping a rule that would otherwise fail immediately, recording today's known violations as an accepted baseline
archunit.properties the three properties that control where the frozen-violation store lives and whether it may be created or updated
archunit_store/ the frozen-violation store itself, committed to version control — this directory is the baseline the rule enforces against

Captured output

File What it shows
00-baseline-passing-run.txt the three structural rules, all green, ~1.3s, no ApplicationContext started
01-layering-violation-failure.txt a real layeredArchitecture() failure, captured by temporarily adding a controller that bypassed the service layer, then deleting it
02-freeze-run1-baseline-captured.txt the first-ever run of FreezingArchRuleTest: no store existed yet, so it was created with today's one known violation already accepted
03-freeze-run2-new-violation-caught.txt a second, genuinely new throws Exception added after the baseline was recorded — the failure names only the new offender, never the frozen one
04-full-suite-final.txt all four rules, exactly as committed, green in under four seconds

A diagnostic note, not a defect

LegacyOrderExporter.export() still declares throws Exception in the committed code, and noNewGenericExceptionsDeclared in FreezingArchRuleTest still technically covers it. The build is green anyway, on purpose: archunit_store/ records that specific violation as already known at the time the rule was turned on, and freeze() only fails a build on a violation that is new since that recording. This is the realistic case — a team adopting ArchUnit on a codebase that is not already clean — and the point the post's freezing section makes is that you do not have to choose between "fix two hundred existing violations today" and "don't bother enforcing the rule at all."

License

MIT, matching the rest of this repository.