Step-by-Step: Implementing Hexagonal Architecture in Spring Boot
"Good architecture isn't about making your code more complicated. It's about making future change less expensive."
As software systems grow, one problem appears over and over again.
Business logic becomes tightly coupled to frameworks, databases, REST APIs, messaging systems, and external services.
Eventually, every change becomes risky.
Need to replace MySQL with PostgreSQL? Unexpected refactoring.
Need to expose GraphQL instead of REST? Business logic changes.
Need to introduce Kafka? Half the application gets modified.
This isn't a Spring Boot problem.
It's an architecture problem.
One of the most effective ways to solve it is Hexagonal Architecture (also called Ports and Adapters).
After applying it in multiple enterprise systems, I've found that it dramatically improves maintainability, testability, and long-term flexibility.
Here's a practical step-by-step guide to implementing it in Spring Boot.
What Is Hexagonal Architecture?
Hexagonal Architecture was introduced by Alistair Cockburn.
Its central idea is simple:
Business logic should not depend on external technologies. External technologies should depend on the business logic.
Instead of building applications around Spring, JPA, REST, or Kafka, we build them around the domain.
Everything else becomes replaceable.
Think of your application as a hexagon.
Outside the hexagon are adapters:
REST APIs
Databases
Message brokers
External services
Command-line tools
Inside the hexagon lives only business logic.
The business logic doesn't know or care who calls it.
Step 1: Define the Domain
The domain contains:
Entities
Value Objects
Business Rules
Domain Services
Example:
public class Order {
private OrderId id;
private CustomerId customerId;
private List<OrderItem> items;
public void confirm() {
// business rules
}
}
Notice what's missing.
No:
@Entity
@Autowired
@Component
Spring annotations
JPA imports
The domain is pure Java.
This makes it easy to test and independent of any framework.
Step 2: Define Input Ports (Use Cases)
A port represents what the application can do.
Example:
public interface CreateOrderUseCase {
Order create(CreateOrderCommand command);
}
This is the API of your application.
Controllers call this interface.
Tests call this interface.
Even scheduled jobs call this interface.
Nobody depends on implementation details.
Step 3: Define Output Ports
Business logic often needs external systems.
Instead of calling repositories directly, define interfaces.
Example:
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(OrderId id);
}
Notice:
The domain defines the interface.
Infrastructure implements it.
Dependency direction remains inward.
Step 4: Implement the Use Case
Application services coordinate business logic.
@Service
public class CreateOrderService
implements CreateOrderUseCase {
private final OrderRepository repository;
public Order create(CreateOrderCommand command) {
Order order = Order.create(command);
return repository.save(order);
}
}
Notice something interesting.
This service doesn't know:
JPA
SQL
PostgreSQL
MongoDB
It only knows the repository interface.
Step 5: Build the REST Adapter
Now expose the use case through HTTP.
@RestController
@RequestMapping("/orders")
public class OrderController {
private final CreateOrderUseCase useCase;
}
The controller translates:
HTTP → Domain
That's all.
No business rules should live here.
Step 6: Build the Database Adapter
Infrastructure implements the output port.
@Repository
public class JpaOrderRepository
implements OrderRepository {
}
Internally, it may use:
Spring Data JPA
Hibernate
JDBC
MyBatis
The domain never notices.
Tomorrow you can replace JPA entirely.
Step 7: Add Messaging
Suppose you publish events.
Instead of:
kafkaTemplate.send(...)
inside business logic...
Define another output port.
public interface EventPublisher {
void publish(OrderCreatedEvent event);
}
Infrastructure implements it.
KafkaEventPublisher
Later you can replace Kafka with RabbitMQ or AWS SNS without touching business logic.
Step 8: Organize the Project Structure
A common Spring Boot layout looks like this:
src
├── domain
│ ├── model
│ ├── ports
│ └── services
│
├── application
│ ├── usecases
│ └── services
│
├── adapters
│ ├── inbound
│ │ ├── rest
│ │ └── messaging
│ │
│ └── outbound
│ ├── persistence
│ ├── kafka
│ └── clients
│
└── config
The separation becomes very clear.
Step 9: Testing Becomes Easy
Testing business logic becomes trivial.
Instead of starting Spring Boot...
Instead of connecting databases...
Instead of mocking HTTP...
Simply mock the ports.
OrderRepository repository = mock(...);
CreateOrderService service =
new CreateOrderService(repository);
Tests become:
Fast
Deterministic
Independent
Many complete in milliseconds.
Step 10: Replace Infrastructure Without Fear
Imagine these changes:
✅ REST → GraphQL
✅ MySQL → PostgreSQL
✅ Kafka → RabbitMQ
✅ Local Storage → AWS S3
✅ SMTP → SendGrid
In a layered architecture, these changes often ripple through multiple layers.
In Hexagonal Architecture, most changes stay confined to adapters.
The core business logic remains untouched.
That's the real payoff.
Common Mistakes
I've reviewed many projects claiming to use Hexagonal Architecture but making these mistakes:
❌ JPA annotations inside domain entities
❌ Business logic inside controllers
❌ Spring dependencies everywhere
❌ Repository interfaces defined in infrastructure
❌ Domain objects returning HTTP responses
❌ Services calling Kafka directly
If your domain knows about Spring, it isn't truly framework-independent.
When Should You Use It?
Hexagonal Architecture is an excellent fit for:
Enterprise applications
Microservices
Financial systems
Healthcare platforms
Long-lived products
Complex business domains
It may be unnecessary for:
Small CRUD applications
Short-lived prototypes
Internal tools with limited complexity
Architecture should match the problem—not every project needs maximum abstraction.
Final Thoughts
Hexagonal Architecture isn't about adding layers for the sake of elegance.
It's about protecting what changes the least—your business rules—from what changes the most—frameworks, databases, APIs, and infrastructure.
Spring Boot will continue to evolve.
Databases will change.
Cloud providers will change.
Messaging platforms will change.
But your business logic should remain stable.
Design your application so that technology is a plugin, not the foundation.
That's the essence of Hexagonal Architecture.
