# 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:

```plaintext
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:

```plaintext
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:

```plaintext
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.

```plaintext
@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.

```plaintext
@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.

```plaintext
@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:

```plaintext
kafkaTemplate.send(...)
```

inside business logic...

Define another output port.

```plaintext
public interface EventPublisher {

    void publish(OrderCreatedEvent event);

}
```

Infrastructure implements it.

```plaintext
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:

```plaintext
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.

```plaintext
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.
