A simple Spring Boot multi-module project demonstrating Hexagonal Architecture / Ports and Adapters.
The goal of this project is to keep business logic independent from frameworks, database, web layer, and infrastructure details.
hexagonal-architecture
├── core
├── web-adapters
├── database-adapters
└── bootstrap
Contains the application core:
- Domain models
- Business rules
- Use cases
- Input ports
- Output ports
- Application services
The core module does not depend on Spring Web, JPA, MySQL, REST, or any adapter.
Contains the input adapter for REST API.
Responsibilities:
- Receive HTTP requests
- Convert requests to application commands
- Call input ports / use cases
- Return HTTP responses
Depends on:
web-adapters → core
Contains the output adapter for persistence.
Responsibilities:
- JPA entities
- Spring Data repositories
- Persistence adapter implementations
- Mapping between domain models and database entities
Depends on:
database-adapters → core
Runnable Spring Boot application module.
Responsibilities:
- Main application class
- Runtime configuration
- Bean wiring
- Connecting all modules together
Depends on:
bootstrap → core
bootstrap → web-adapters
bootstrap → database-adapters
The most important rule:
Dependencies always point toward the core.
┌──────────────────┐
│ bootstrap │
│ Spring Boot App │
└───────┬────┬─────┘
│ │
┌──────────────┘ └──────────────┐
▼ ▼
┌──────────────────────┐ ┌────────────────────────┐
│ web-adapters │ │ database-adapters │
│ Controller, Request │ │ Entity, JpaRepository │
└───────────┬──────────┘ └───────────┬────────────┘
│ │
└──────────────┬──────────────────┘
▼
┌────────────┐
│ core │
│ business │
└────────────┘
The core does not know who calls it or where data is stored.
HTTP / REST → web-adapters → core
Database / JPA → database-adapters → core
core → no adapter dependency
Input adapters call application use cases through input ports.
HTTP Request
↓
Web Adapter
↓
Input Port
↓
Use Case
↓
Domain Logic
Application services access external systems through output ports.
Use Case
↓
Output Port
↓
Persistence Adapter
↓
Database
The project uses a simple Car Listing example.
Main business actions:
- Create car listing
- Get car listing
- Approve car listing
- Reject car listing
Business rules:
- New listing is created with
PENDINGstatus. - Only
PENDINGlisting can be approved. - Only
PENDINGlisting can be rejected. - Approved listing cannot be rejected.
- Rejected listing cannot be approved.
OUTSIDE WORLD
│
▼
┌─────────────────┐
│ web-adapters │
│ REST Adapter │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Input Ports │
│ Use Cases │
└────────┬────────┘
│
▼
┌─────────────────┐
│ core │
│ Business Logic │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Output Ports │
└────────┬────────┘
│
▼
┌─────────────────────────┐
│ database-adapters │
│ JPA / Persistence Layer │
└────────────┬────────────┘
│
▼
MySQL
- Java 26
- Spring Boot 4.1.0
- Gradle Multi-Module
- Spring Web MVC
- Spring Data JPA
- MySQL
- Docker Compose
- Lombok
docker compose up -d./gradlew buildWindows:
.\gradlew.bat build./gradlew :bootstrap:bootRunWindows:
.\gradlew.bat :bootstrap:bootRunBusiness logic stays in the core.
Framework and infrastructure details stay outside as adapters.
Good dependency direction:
web-adapters → core
database-adapters → core
bootstrap → core + adapters
Bad dependency direction:
core → web-adapters
core → database-adapters
core → Spring MVC
core → JPA
core → MySQL
This project demonstrates how to structure a Spring Boot application using Hexagonal Architecture.
The core contains the business logic and defines what it needs through ports.
Adapters implement those ports using real technologies such as REST, JPA, and MySQL.
This keeps the application easier to test, maintain, and extend.