Hexagonal Architecture in Java: Clean Logic Without Pain

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Your Application Is Not Its Database

\n\n

Imagine you are building a house. Foundation, walls, roof — everything is well thought out. But a year later, you decide to change the wiring. And then it turns out that the wires are embedded in concrete, and the sockets are welded to the rebar. To replace one switch, you need to demolish half the house.

\n\n

This is roughly what a classic Java application looks like, where business logic is tightly intertwined with the framework, database, or REST controller. You want to switch from PostgreSQL to MongoDB? Rewrite controllers from Spring MVC to JAX-RS? Welcome to refactoring hell.

\n\n

The solution is Hexagonal architecture (also known as "Ports and Adapters", also known as "Clean Architecture" in Alistair Cockburn's interpretation). It's not a silver bullet, but it's a way to make your application flexible, testable, and independent of the outside world.

\n\n

Today we'll break down Hexagonal architecture in Java with real code examples, no fluff or abstract reasoning. Let's go.

\n\n

What is Hexagonal Architecture (Ports and Adapters)?

\n\n

The essence is simple: your application is a core that contains business logic. Around the core is a "hexagon" (actually an arbitrary number of sides), through which the outside world communicates with the core.

\n\n

Each side has its own port (interface). And an adapter is the implementation of the port for a specific technology. For example:

\n
    \n
  • Port "UserRepository" — adapter "JpaUserRepository" or "MongoUserRepository"
  • \n
  • Port "SendNotification" — adapter "EmailSender" or "SmsSender"
  • \n
\n\n

The main rule: the core knows nothing about external libraries, databases, or web frameworks. It only knows its ports (interfaces). This provides several advantages:

\n
    \n
  • Infrastructure independence — change the DB or framework without touching the logic.
  • \n
  • Testability — business logic can be tested without starting the Spring context or database.
  • \n
  • Readability — the core code tells the domain story, not technical details.
  • \n
\n\n

Project Structure: How to Organize the Code

\n\n

There is no single correct package structure, but I propose a proven option based on DDD (Domain-Driven Design) and Hexagonal principles:

\n\n
com.example.app\n├── domain\n│   ├── model          // Entities, Value Objects\n│   ├── port           // Port interfaces (inbound/outbound)\n│   └── service        // Business services (use cases)\n├── adapter\n│   ├── inbound\n│   │   ├── rest       // REST controllers (inbound adapters)\n│   │   └── messaging  // Kafka/RabbitMQ listeners\n│   └── outbound\n│       ├── persistence // JPA, JDBC, MongoDB repositories\n│       └── client      // HTTP clients, external APIs\n└── config             // Spring configuration (if you use it)\n
\n\n

Note: domain does not import anything from adapter. This is critical. If you see @Entity or @RestController in domain, the architecture is broken.

\n\n

Example: Order Management System

\n\n

Let's design a simple system: a user can create an order, and the system checks product availability in the warehouse and sends a notification.

\n\n

Step 1. Domain Model (domain/model)

\n\n
// Order.java\npublic class Order {\n    private final OrderId id;\n    private final List<OrderItem> items;\n    private OrderStatus status;\n\n    public Order(List<OrderItem> items) {\n        this.id = new OrderId(UUID.randomUUID().toString());\n        this.items = Collections.unmodifiableList(items);\n        this.status = OrderStatus.PENDING;\n    }\n\n    public void confirm() {\n        if (status != OrderStatus.PENDING) {\n            throw new IllegalStateException("Only PENDING orders can be confirmed");\n        }\n        this.status = Ord                

Blogs

Book Recommendations