Clean Architecture
Dependency Rule
Java
System Boundaries
Architectural Styles
Clean architecture is strongest when teams need a memorable rule: source code dependencies should point inward toward policy, not outward toward frameworks.
Clean Architecture overlaps with hexagonal and onion architecture, but it is especially influential because it packages the idea around the dependency rule and around clear separation between entities, use cases, interface adapters, and frameworks.
Why This Topic Matters
Why it matters in real Java systems
Java teams often become framework-first without realizing it. Clean architecture is useful because it keeps asking whether the application logic depends on Spring, persistence, HTTP, or messaging details more than it should.
Core Explanation
The main architectural idea
The best-known idea in clean architecture is the dependency rule: source code dependencies should always point inward. Outer layers may know about inner layers, but not the reverse. This helps keep business policy stable while outer delivery and infrastructure mechanisms evolve.
The terminology often includes entities, use cases, interface adapters, and frameworks. The exact labels matter less than the rule they represent. If the business core knows too much about frameworks, the architecture is no longer very clean.
Concept Breakdown
Key ideas to keep in mind
Concept
Entities
Core business concepts and rules that should remain stable and expressive.
Concept
Use cases
Application-specific workflows that coordinate what the system should do.
Concept
Interface adapters
Controllers, presenters, gateways, and mappers that translate between the core and the outside world.
Diagram in words
Diagram in words
Read the rings from inside out: entities hold the business policy, use cases coordinate application behavior, interface adapters translate to and from HTTP, storage, and UI models, and frameworks live at the outer edge as tools rather than the center of the design.
Java Example
Java example in the Order Management domain
Java example: use case isolated from framework annotations
package org.javaomnibus.ecommerce.usecase;
public final class RefundOrderUseCase {
private final RefundGateway refundGateway;
private final OrderStore orderStore;
public RefundOrderUseCase(RefundGateway refundGateway, OrderStore orderStore) {
this.refundGateway = refundGateway;
this.orderStore = orderStore;
}
public RefundReceipt refund(String orderId) {
Order order = orderStore.requireById(orderId);
RefundReceipt receipt = refundGateway.refund(order.paymentReference());
order.markRefunded(receipt.reference());
orderStore.save(order);
return receipt;
}
}
Code Walkthrough
Step by step
- The use case is plain Java and depends only on business-facing abstractions.
- Framework-specific controllers or beans can call this use case from the outside.
- That makes the application rule easier to test and reason about.
- This is the practical meaning of keeping frameworks at the edge.
Real-World Usage
Where this helps in practice
- Teams that want framework churn to affect as little business code as possible
- Systems with high emphasis on use-case boundaries and test isolation
- Architectures where controllers, presenters, and gateways are treated as adapters around the core
Trade-Offs
Trade-offs and when not to overuse it
- Clean architecture improves conceptual discipline, but it can become over-layered if every simple interaction demands too many translation steps.
- The style pays off most when business rules are important and expected to survive multiple infrastructure changes.
Common Mistakes
Frequent mistakes to watch for
- Adding the vocabulary without enforcing the inward dependency rule
- Confusing package names with architectural cleanliness
- Creating presenter and gateway abstractions that add no real value
- Overcomplicating simple applications that do not have strong policy or longevity needs
Related Pages
Related concepts
What To Read Next