skip to content

Hexagonal / Ports & Adapters

The application core sits in the middle, driving and driven ports define its edges, and adapters plug the outside world in. You will learn why this makes the core testable without a database or web server, and how it differs from plain layering.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

In Hexagonal Architecture (Ports & Adapters), what is a 'port', and what distinguishes a driving (primary) port from a driven (secondary) port?

level: juniorimportance: must knowfreq 75%

answer

  1. driving = called BY adapter, INTO core
  2. driven = called BY core, OUT to adapter
  3. port = interface owned by core
  4. Cockburn 2005
  5. left side vs right side

basics

~20 s

A port is an interface the core app defines. Driving ports are ways the outside world calls INTO the app (like an API). Driven ports are ways the app calls OUT to things it needs (like a database).

solid answer

~30 s

A port is an interface owned by the application core that defines a contract, independent of any technology. Driving (primary) ports are entry points — interfaces the core exposes so outside actors (UI, REST controller, CLI, message consumer) can invoke its use cases. Driven (secondary) ports are exit points — interfaces the core defines for capabilities it needs from the outside world (persistence, email, payment gateway), which the core calls but does not implement. The key distinction is direction: driving ports are called BY adapters, driven ports are called BY the core and implemented BY adapters.

go deeper

for a junior

Should know a port is an interface and roughly which direction driving vs driven point, even if hazy on which side 'owns' the interface.

for a middle

Should correctly state that both port types are defined by the core, and give one clear own example of each without prompting.

for a senior

Should be able to design a well-shaped port from a use case, spot a leaky port in review, and justify when the split is/isn't worth it.

for a principal

Should reason about port granularity and boundary placement across a whole system, and set conventions that prevent port explosion or leakage across teams.

## What a port is Hexagonal Architecture, introduced by **Alistair Cockburn** in 2005, organizes an application around a central "application core" — the business logic and use cases — that is completely isolated from the technical details of how it's invoked or what it depends on. The mechanism for that isolation is the **port**: an interface, defined and owned by the core, that describes a capability in terms the business understands, with zero mention of any specific technology (no SQL, no HTTP, no message broker library). ## The two directions Ports split into two kinds based on the direction of the call. - **A driving port** (also called a primary or "left-side" port) is a contract the core exposes outward so something in the outside world can trigger it — think `PlaceOrderUseCase` with a method `placeOrder(command): OrderResult`. Adapters like a REST controller, a CLI command handler, or a Kafka consumer sit on the "driving" side; they translate an external request format into a call against this port, and the core's implementation of that port runs the actual business logic. - **A driven port** (secondary, "right-side" port) runs in the opposite direction: it's a contract the core defines because it needs something FROM the outside world — for example, an `OrderRepository` interface with `save(order)` and `findById(id)`, or a `PaymentGateway` interface with `charge(amount, method)`. The core calls methods on this interface, but the core does not implement it; a driven adapter — a JPA repository implementation, a Stripe API client — does, and is plugged in at the boundary. ## Why this exists The problem being solved is that in naive designs, business logic ends up directly calling framework and infrastructure code (a service class instantiating a `JdbcTemplate`, or calling `HttpServletRequest` directly), which means changing the database vendor, the web framework, or adding a second delivery mechanism forces changes deep inside the business logic. By making the core define its own contracts and never import a concrete infrastructure class, hexagonal architecture forces all dependency arrows to point **INTO** the core, regardless of whether the interaction is "driving" (core is called) or "driven" (core does the calling). This is what Cockburn meant by treating all technology adjacencies symmetrically — a UI and a database are both "just adapters" from the core's point of view, even though a UI drives the app and a database is driven by it. ## The trade-off The trade-off is **indirection and volume**: every capability the core needs gets an interface plus at least one concrete adapter, and every entry point gets an interface plus at least one adapter that calls it. For a small CRUD service with one UI and one database, this can roughly double the number of types for no immediate payoff — you're paying an abstraction tax before you have a second implementation to justify it. The benefit shows up when you actually need multiple adapters on either side, e.g.: - exposing the same `PlaceOrder` use case via both a REST API and an internal gRPC service — two driving adapters sharing one driving port and one core implementation; - swapping a real payment gateway for a test double or a different vendor — two driven adapters implementing one driven port — without touching business logic. ## Failure modes Failure modes show up when the port/adapter boundary is drawn in name only. 1. **A common one is a port interface that leaks adapter-specific concerns** — an `OrderRepository` port with a method like `findByJpqlQuery(String)` or `save()` returning a JPA entity — which means the "port" is really just the adapter's own interface relabeled, and swapping persistence technology still requires touching the core. 2. **Another is direction confusion**: teams sometimes have the core depend on a driving port's adapter (e.g., the core reaching back into a web-layer DTO), which reintroduces the exact coupling ports exist to prevent. 3. **A third is port explosion**: defining a separate driven port per external call site rather than per cohesive capability, which turns the core into an interface-heavy maze that's harder to navigate than the framework code it replaced. ## Putting it together A concrete illustration: an e-commerce checkout use case exposed both through a synchronous REST API and an asynchronous "abandoned cart" background job — both are driving adapters calling the same driving port (`CheckoutUseCase`). That use case, in turn, depends on driven ports for: - `InventoryPort` (checking stock) - `PaymentPort` (charging a card) - `OrderRepositoryPort` (persisting the order) In production these are backed by a real inventory microservice client, a Stripe adapter, and a Postgres repository, while in tests they're backed by in-memory fakes, letting the entire checkout business rule set be tested with no HTTP server, no real database, and no network call to Stripe — which is the testability payoff hexagonal design is chiefly valued for.

  • If a driven port interface exposes a method like `findByRawSql(String query)`, what's wrong with it?
    It leaks a persistence-technology concern (SQL) into a contract that's supposed to be technology-agnostic, meaning the core's business logic is now implicitly coupled to a relational database and raw query strings. Swapping to a document store or an in-memory test double becomes awkward or impossible without redesigning the port. A well-designed port instead exposes intent, e.g., findActiveOrdersForCustomer(customerId).
  • Can the same driving port have multiple adapters at once, and why would you do that?
    Yes — e.g., a REST controller and a gRPC handler can both call the same PlaceOrderUseCase port, translating their own request formats into the port's method call. You'd do this to expose one piece of business logic through multiple delivery mechanisms without duplicating or forking the logic itself.
  • Where does the port interface physically live in code — core module or adapter module?
    In the application core (or a shared 'domain'/'application' package the core owns), never in the adapter. The core defines the contract because it's the one with the requirement; adapters depend on the core's port definition, not the other way around, which is what keeps the dependency arrow pointing inward.

Think of the core as a power outlet on a wall (the 'hexagon'). Driving ports are like the outlet's socket shape — anything plugged in (a lamp, a phone charger) can trigger power to flow. Driven ports are like a cord the outlet itself plugs into to draw electricity from the grid — the outlet defines what kind of plug it needs, but doesn't care whether the grid comes from a coal plant or a solar panel.

saying these in an interview costs you the question

  • Says 'port' and 'adapter' are interchangeable terms with no directional distinction
  • Puts persistence-specific types (e.g. JPA entities, SQL) directly in a driven port signature
  • Thinks a driving port is something the core calls outward
  • Can't name a concrete example of a driving vs a driven port
  • Believes you need exactly one adapter per port for the pattern to be worthwhile

context

open as a page

In Hexagonal Architecture, why does the application core never hold a compile-time dependency on any adapter, and what mechanism makes that possible given that at runtime the core's logic still has to run inside a real web server or database transaction?

level: middleimportance: must knowfreq 70%

basics

~20 s

The core only knows about interfaces it defines itself, never concrete tech classes. A separate wiring step (like a dependency-injection setup) plugs the real adapters in at startup, so the core's code never imports database or web code directly.

open as a page

You're designing a 'TransferFunds' use case between two bank accounts using Hexagonal Architecture. Walk through what the driving port, the application core's responsibilities, and the driven ports would be, and explain why the application core is described as the 'dependency anchor' of the whole design.

level: seniorimportance: must knowfreq 60%

basics

~20 s

The core holds the transfer rules (enough balance? valid accounts?). A driving port lets something like an API call 'do this transfer.' Driven ports let the core ask for things it needs, like reading and saving account balances, without knowing how those things are actually done.

open as a page

How does Hexagonal Architecture make it easier to write fast, reliable unit tests for business logic compared to a typical layered (controller to service to repository) architecture where the service layer directly calls concrete repository and client classes?

level: middleimportance: should knowfreq 65%

basics

~20 s

Because business logic only talks to interfaces, tests can swap in fake, in-memory versions of the database or payment gateway instead of the real ones, so tests run fast with no network or database needed, while still checking real business rules.

open as a page

What are the concrete costs of adopting Hexagonal Architecture for a service, and what characteristics of a project should make a team hesitate before applying it?

level: seniorimportance: should knowfreq 55%

basics

~20 s

It means more files and interfaces for simple stuff, and it only pays off if you actually need to swap technology or test business logic heavily. For a tiny app with one database and one UI that will never change, it's often just extra work with no real benefit.

open as a page

What are some common anti-patterns that appear when Hexagonal Architecture is applied across a larger codebase or multiple teams, beyond simple leaky ports, and how do they undermine the pattern's intent?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

At scale, teams often make too many tiny interfaces (a mess to navigate), write fakes that don't match real behavior, or build one giant port instead of several focused ones — each quietly brings back the same coupling and confusion the pattern was meant to prevent.

open as a page