Full Stack • Java • System Design • Cloud • AI Engineering

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
  • Email
  • 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.


Loading likes...

Comments

Share a question, correction, or practical insight about this article.

Loading approved comments...