clean-architecture-java · git:20261009.6009e76 · 2026-10-09 · sha256 9f541e7b2f2184a5

clean-architecture-java git:20261009.6009e76A

Immutable. This exact content never changes and is served at /api/v1/blob/9f541e7b2f2184a5.

---
name: clean-architecture-java
description: Use when laying out or extending a Java / Spring Boot service built with Maven — which module a class belongs in, which module may depend on which, and how an endpoint reaches a use case.
---

# Clean Architecture — Java / Spring Boot

The layer rules are those of `architecture-patterns`. This skill maps them onto Maven and Spring.

## One Maven module per layer

| Module | Holds | Only `<dependency>` on |
|---|---|---|
| `<context>-domain` | aggregates, value objects, domain events | nothing |
| `<context>-application` | use cases and the interfaces they call | `<context>-domain` |
| `<context>-infrastructure` | implementations of those interfaces (JPA, HTTP clients, files) | `<context>-application` |
| `<context>-api` | `@SpringBootApplication`, controllers, `@Configuration` wiring | `<context>-infrastructure` |

- Import types reached transitively; never add a `<dependency>` to reach them.
- Spring, Jakarta and JPA dependencies appear only in the `-infrastructure` and `-api` poms.
- Tests live in the two test modules `clean-architecture-testing` defines, never in a layer module.

## Use cases

- A use case is a `final` class named after the action (`BorrowBook`). The controller calls it directly: no interface in front of it, no `*Service` implementation.
- A command method returns `void`; the caller creates the new id and passes it in. A query returns a `*ViewModel` record built in Application, never a domain object.
- Application declares an interface only where it calls out (repository, gateway). A repository interface sits in Domain when it persists an aggregate, in Application otherwise.
- Who may *read* a resource is checked in the use case. Who may *change* it is an aggregate rule: the mutation method takes the caller's id and throws.
- Wire every class with `@Bean` methods in `-api`; Domain and Application classes carry no Spring annotation.
- One transaction per use-case call, opened in `-api` (`TransactionTemplate` around the call, or `@Transactional` on the controller method). Never add an interface to a use case to decorate it.
- Inside each module, one package per feature (`…application.loan`, `…infrastructure.loan`), never a technical one (`repository`, `service`, `persistence`). A use case imports no other feature's package; what two features use moves to a `shared` package of that module.
- No `port`, `adapter`, `in` or `out` packages, and no "port" in type names or comments.

## Domain

- An aggregate is created through a static factory over a `private` constructor, and identified by a typed id record (`OrderId`), never a raw `String` or `UUID`.

## Guard

The module graph and the Spring-free core are enforced by the pom and ArchUnit tests of the `clean-architecture-testing` skill (its `references/examples-java.md`).