Hexagonal Architecture (Ports and Adapters)
Learn Hexagonal Architecture (Ports & Adapters) from the ground up. Understand ports, adapters, inbound/outbound adapters, dependency inversion, Spring Boot implementation, testing strategies, Clean Architecture comparison, DDD integration, and real-world enterprise architectures used in Banking, Amazon, Netflix, Uber, and Microservices.
Introduction
Imagine you're building a Digital Banking Platform.
Your application currently exposes REST APIs.
Client
↓
Spring REST Controller
↓
Business Logic
↓
PostgreSQL
Everything works well.
Six months later...
The business asks you to support:
- Kafka Events
- GraphQL
- gRPC APIs
- Mobile App
- Batch Jobs
- AWS Lambda
- Redis
- MongoDB
If your business logic depends directly on Spring MVC or PostgreSQL,
every new technology requires major code changes.
How can we prevent this?
The solution is Hexagonal Architecture, also known as Ports and Adapters Architecture.
Learning Objectives
After completing this article, you'll understand:
- What is Hexagonal Architecture?
- Why Hexagonal Architecture?
- Ports
- Adapters
- Inbound Adapters
- Outbound Adapters
- Dependency Rule
- Spring Boot Implementation
- Package Structure
- Testing Strategy
- Clean Architecture vs Hexagonal
- DDD Integration
- Real-world Examples
What is Hexagonal Architecture?
Hexagonal Architecture was introduced by Alistair Cockburn.
Its main principle is:
Business logic should not depend on external technologies.
Instead,
the application communicates through Ports.
Technologies connect using Adapters.
Traditional Layered Architecture
flowchart TD
CLIENT[Client]
CONTROLLER[Controller]
SERVICE[Service]
REPOSITORY[Repository]
DATABASE[(Database)]
CLIENT --> CONTROLLER
CONTROLLER --> SERVICE
SERVICE --> REPOSITORY
REPOSITORY --> DATABASE
Business logic becomes tightly coupled with frameworks.
Hexagonal Architecture
flowchart LR
CLIENT[REST / Kafka / gRPC]
INBOUND[Inbound Adapter]
PORT[Application Port]
DOMAIN[Business Logic]
OUTPORT[Outbound Port]
OUTBOUND[Outbound Adapter]
DATABASE[(Database)]
CLIENT --> INBOUND
INBOUND --> PORT
PORT --> DOMAIN
DOMAIN --> OUTPORT
OUTPORT --> OUTBOUND
OUTBOUND --> DATABASE
Notice that the Domain never depends on the database or framework.
Why is it Called Hexagonal?
The hexagon is symbolic.
It shows the application can have multiple entry and exit points.
Example:
REST
GraphQL
Kafka
gRPC
Batch
CLI
WebSocket
All communicate through Ports.
Core Building Blocks
flowchart TD
HEX[Hexagonal Architecture]
PORTS[Ports]
ADAPTERS[Adapters]
DOMAIN[Domain]
HEX --> PORTS
HEX --> ADAPTERS
HEX --> DOMAIN
What is a Port?
A Port is an interface that defines how the application communicates.
Think of it as a contract.
Example
public interface TransferMoneyUseCase {
void transfer(TransferRequest request);
}
The business defines what should happen,
not how.
Types of Ports
There are two types:
- Inbound Ports
- Outbound Ports
Inbound Ports
Inbound Ports define
How external systems invoke business logic.
Examples
- REST API
- Kafka Consumer
- GraphQL
- Scheduler
- CLI
Inbound Adapter
flowchart LR
CLIENT[Client]
REST[REST Controller]
PORT[Transfer Port]
CLIENT --> REST
REST --> PORT
The REST controller simply delegates.
Example Controller
@RestController
@RequestMapping("/accounts")
public class AccountController {
private final TransferMoneyUseCase useCase;
@PostMapping("/transfer")
public ResponseEntity<Void> transfer(
@RequestBody TransferRequest request) {
useCase.transfer(request);
return ResponseEntity.ok().build();
}
}
Notice:
No business logic inside the controller.
Outbound Ports
Outbound Ports define
How the business accesses external systems.
Examples
- Database
- Redis
- Kafka Producer
- Payment Gateway
Repository Port
public interface AccountRepository {
Account findById(Long id);
void save(Account account);
}
This interface belongs to the Domain.
Outbound Adapter
flowchart LR
DOMAIN[Business Logic]
PORT[Repository Port]
JPA[JPA Adapter]
DB[(PostgreSQL)]
DOMAIN --> PORT
PORT --> JPA
JPA --> DB
Spring Data JPA is hidden behind the adapter.
JPA Adapter
@Repository
public class JpaAccountRepository
implements AccountRepository {
}
Tomorrow,
PostgreSQL can be replaced with MongoDB
without changing the Domain.
Dependency Rule
Dependencies always point inward.
flowchart TD
DATABASE[(Database)]
SPRING[Spring Boot]
REST[REST]
PORTS[Ports]
DOMAIN[Domain]
DATABASE --> SPRING
SPRING --> REST
REST --> PORTS
PORTS --> DOMAIN
The Domain knows nothing about Spring.
Complete Request Flow
sequenceDiagram
participant Client
participant REST
participant UseCase
participant Repository
participant PostgreSQL
Client->>REST: POST /transfer
REST->>UseCase: transfer()
UseCase->>Repository: save()
Repository->>PostgreSQL: INSERT
PostgreSQL-->>Repository: Success
Repository-->>UseCase: Saved
UseCase-->>REST: Success
REST-->>Client: 200 OK
Package Structure
src
├── domain
│ ├── entity
│ ├── port
│ └── service
│
├── application
│
├── adapters
│ ├── in
│ │ ├── rest
│ │ ├── kafka
│ │ └── graphql
│ │
│ └── out
│ ├── persistence
│ ├── redis
│ ├── kafka
│ └── email
│
└── config
Spring Boot Project Structure
bank-service
src/main/java
domain
application
adapters
config
pom.xml
Banking Example
Money Transfer
flowchart TD
CUSTOMER[Customer]
REST[REST API]
TRANSFER[Transfer Use Case]
ACCOUNT[Account Entity]
PORT[Repository Port]
JPA[JPA Adapter]
POSTGRES[(PostgreSQL)]
CUSTOMER --> REST
REST --> TRANSFER
TRANSFER --> ACCOUNT
TRANSFER --> PORT
PORT --> JPA
JPA --> POSTGRES
Multiple Input Adapters
The same business logic can be reused.
flowchart LR
REST[REST API]
GRAPHQL[GraphQL]
KAFKA[Kafka Consumer]
USECASE[Transfer Use Case]
REST --> USECASE
GRAPHQL --> USECASE
KAFKA --> USECASE
No duplicated business logic.
Multiple Output Adapters
flowchart LR
USECASE[Business Logic]
DB[(PostgreSQL)]
CACHE[(Redis)]
EMAIL[Email Service]
EVENT[Kafka]
USECASE --> DB
USECASE --> CACHE
USECASE --> EMAIL
USECASE --> EVENT
Business remains technology independent.
Testing
One of the biggest advantages.
Test business logic
WITHOUT
- Spring Boot
- Database
- Kafka
Example
@Test
void shouldTransferMoney(){
AccountRepository repository = new FakeRepository();
TransferMoneyUseCase useCase =
new TransferService(repository);
useCase.transfer(request);
}
Fast unit tests.
Hexagonal vs Layered
| Layered | Hexagonal |
|---|---|
| Framework-centric | Domain-centric |
| Tight Coupling | Loose Coupling |
| Difficult Testing | Easy Testing |
| Controllers call Services directly | Adapters communicate through Ports |
| Database often leaks into business | Business is isolated |
Hexagonal vs Clean Architecture
| Clean Architecture | Hexagonal |
|---|---|
| Concentric circles | Ports & Adapters |
| Focus on dependency rule | Focus on communication boundaries |
| Uses Use Cases | Uses Ports |
| Very similar goals | Very similar goals |
Many teams combine both approaches.
DDD Integration
Hexagonal Architecture works naturally with Domain-Driven Design.
Typical structure
Domain
↓
Entities
↓
Value Objects
↓
Aggregates
↓
Repositories
↓
Ports
↓
Adapters
Spring Boot Integration
flowchart TD
CLIENT[React]
REST[REST Adapter]
PORT[Inbound Port]
DOMAIN[Business Logic]
OUTPORT[Repository Port]
JPA[JPA Adapter]
REDIS[(Redis)]
POSTGRES[(PostgreSQL)]
CLIENT --> REST
REST --> PORT
PORT --> DOMAIN
DOMAIN --> OUTPORT
OUTPORT --> JPA
JPA --> POSTGRES
DOMAIN --> REDIS
Amazon Example
Amazon services expose APIs through adapters while core business logic remains independent of REST, messaging, and storage technologies.
Netflix Example
Recommendation services process requests from REST APIs, scheduled jobs, and streaming events using the same core business logic.
Uber Example
Ride matching logic can be triggered by REST APIs, Kafka events, or scheduled processes without changing the domain layer.
Banking Example
A money transfer use case can be invoked from:
- Mobile Banking
- ATM
- Internet Banking
- Branch Teller System
- Scheduled Settlement Jobs
All use the same domain logic.
Advantages
- Technology Independence
- High Testability
- Loose Coupling
- Replaceable Infrastructure
- Better Maintainability
- Clear Separation of Concerns
- Easier Migration to New Technologies
Challenges
- More Interfaces
- More Boilerplate
- Steeper Learning Curve
- Additional Package Structure
- Requires Good Domain Modeling
Monitoring
Monitor
- Adapter Response Time
- Use Case Execution Time
- Database Latency
- External API Calls
- Kafka Processing Time
- Redis Access Time
- Error Rates
Tools
- Prometheus
- Grafana
- Datadog
- OpenTelemetry
- Jaeger
Common Mistakes
❌ Putting business logic inside controllers
❌ Exposing JPA entities outside adapters
❌ Allowing the domain to depend on Spring annotations
❌ Mixing DTOs with domain entities
❌ Creating ports for every trivial class
❌ Skipping unit tests for the domain layer
Best Practices
- Keep the domain free from framework dependencies.
- Define ports as business contracts.
- Implement infrastructure in adapters.
- Treat databases and messaging systems as replaceable details.
- Use dependency injection only in the outer layers.
- Write unit tests for the domain without starting Spring Boot.
- Combine Hexagonal Architecture with DDD and Clean Architecture for large enterprise systems.
Common Interview Questions
What is Hexagonal Architecture?
Hexagonal Architecture organizes software around the domain using Ports and Adapters, allowing business logic to remain independent of frameworks and infrastructure.
What is a Port?
A Port is an interface that defines how the application communicates with the outside world.
What is an Adapter?
An Adapter is an implementation that connects external technologies (REST, Kafka, JPA, Redis, etc.) to the application's ports.
What is the difference between Inbound and Outbound Adapters?
| Inbound Adapter | Outbound Adapter |
|---|---|
| Receives requests | Calls external systems |
| REST Controller | JPA Repository |
| Kafka Consumer | Kafka Producer |
| GraphQL Resolver | Email Service |
When should Hexagonal Architecture be used?
It is especially useful for:
- Enterprise Applications
- Banking Systems
- Microservices
- Domain-Driven Design
- Applications requiring high testability and long-term maintainability
Summary
Hexagonal Architecture helps build applications where business logic is isolated from technology. By introducing Ports as contracts and Adapters as technology-specific implementations, teams can evolve frameworks, databases, and communication protocols without rewriting core business rules.
In this article, we covered:
- Hexagonal Architecture fundamentals
- Ports
- Inbound and Outbound Adapters
- Dependency Rule
- Request flow
- Package structure
- Spring Boot implementation
- Testing strategy
- Comparison with Layered and Clean Architecture
- DDD integration
- Banking, Amazon, Netflix, and Uber examples
- Best practices
Hexagonal Architecture is widely adopted in modern enterprise Java applications because it supports clean boundaries, independent testing, and technology evolution while keeping the domain model at the center of the system.
Comments
Share a question, correction, or practical insight about this article.
Checking login status...
Loading approved comments...