BackendBench
Coffee

Unravel โ€บ Java concepts โ€บ Design patterns and architecture

Spring

Layered and hexagonal architecture

Enterprise code is usually split into layers (controller, service, repository) so that each class has one job.

Learn these first

At a glance

Mental model

A restaurant: the dining room (controller) takes orders, the kitchen (service) decides how to cook, and the pantry (repository) holds ingredients.

What happens, in order
  1. 1Layered: the controller handles HTTP and validation, the service holds business rules and transactions, the repository talks to the databaseCalls go downward only; a repository never calls a controller.
  2. 2Entities and DTOs cross layers differently: the web layer uses request/response DTOs, the service uses domain objects, the repository uses entities or rows
  3. 3Hexagonal (ports and adapters): the core defines interfaces such as PaymentPort and OrderRepositoryPortAdapters (StripePaymentAdapter, JpaOrderRepository) implement them. The core has no import of Spring or JPA types.
  4. 4Benefits: the core is tested with plain unit tests and fakes, and a technology (database, message broker, payment provider) can be replaced by writing one new adapter
Remember
Watch out
Business rules in controllers or repositoriesCore classes importing framework typesAnaemic services that only delegateLetting layers call upwards or skip layers randomly
Example (highlighted lines explain it)
public interface PaymentPort {    PaymentResult charge(OrderId id, Money amount);}@Servicepublic class CheckoutUseCase {    private final PaymentPort payments;    private final OrderRepositoryPort orders;    public CheckoutUseCase(PaymentPort payments, OrderRepositoryPort orders) {        this.payments = payments;        this.orders = orders;    }    public void checkout(OrderId id) {        Order order = orders.load(id);        payments.charge(id, order.total());        orders.save(order.markPaid());    }}
  1. line 1 uses Port, the core of Layered and hexagonal architecture
  2. line 5 uses Service, the core of Layered and hexagonal architecture
  3. line 6 uses UseCase, the core of Layered and hexagonal architecture
  4. line 7 uses Port, the core of Layered and hexagonal architecture
  5. line 8 uses Repository, the core of Layered and hexagonal architecture
  6. line 10 uses UseCase, the core of Layered and hexagonal architecture
Read the full explanation (about 2 min)

Enterprise code is usually split into layers (controller, service, repository) so that each class has one job. Hexagonal architecture goes further: the business core depends on interfaces (ports) and the database or web framework plugs in from outside (adapters).

A mental model

A restaurant: the dining room (controller) takes orders, the kitchen (service) decides how to cook, and the pantry (repository) holds ingredients. In the hexagonal version the kitchen only knows "I need flour" (a port) and does not care which supplier delivers it.

How it works

Layered: the controller handles HTTP and validation, the service holds business rules and transactions, the repository talks to the database. Calls go downward only; a repository never calls a controller.

Entities and DTOs cross layers differently: the web layer uses request/response DTOs, the service uses domain objects, the repository uses entities or rows.

Hexagonal (ports and adapters): the core defines interfaces such as PaymentPort and OrderRepositoryPort. Adapters (StripePaymentAdapter, JpaOrderRepository) implement them. The core has no import of Spring or JPA types.

Benefits: the core is tested with plain unit tests and fakes, and a technology (database, message broker, payment provider) can be replaced by writing one new adapter.

Going deeper

Do not add layers without purpose. A service that only forwards calls to a repository adds noise; start simple and extract when behaviour appears.

Transactions belong on the service (the unit of business work), not on controllers or repositories. A controller that opens a transaction hides slow I/O inside it.

Enforce boundaries with tests (ArchUnit rules such as "domain must not depend on web") or modules (Spring Modulith), otherwise they erode with every shortcut.

Under the hood

Dependency inversion makes the arrow point inward: source code dependencies go from adapters to the core, while at runtime control flows both ways. The container wires the implementation of each port into the core at startup, which is exactly what dependency injection is for.

Common mistakes

  • Business rules in controllers or repositories.
  • Core classes importing framework types.
  • Anaemic services that only delegate.
  • Letting layers call upwards or skip layers randomly.

How Unravel recognises it

When you paste code, Unravel looks for these signs of layered and hexagonal architecture. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is Layered and hexagonal architecture?

Enterprise code is usually split into layers (controller, service, repository) so that each class has one job. Hexagonal architecture goes further: the business core depends on interfaces (ports) and the database or web framework plugs in from outside (adapters).

What are the most common mistakes with Layered and hexagonal architecture?

Business rules in controllers or repositories. Core classes importing framework types. Anaemic services that only delegate. Letting layers call upwards or skip layers randomly.

How can I see Layered and hexagonal architecture in real code?

Paste your own Java into Unravel and it will point out where layered and hexagonal architecture appears, explain how it works at the detail level you choose, and draw the flow of the code around it.