Introduction: Why Your Application Is Not Its Database
\n\nImagine 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\nThis 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\nThe 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\nToday we'll break down Hexagonal architecture in Java with real code examples, no fluff or abstract reasoning. Let's go.
\n\nWhat is Hexagonal Architecture (Ports and Adapters)?
\n\nThe 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\nEach 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
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
Project Structure: How to Organize the Code
\n\nThere is no single correct package structure, but I propose a proven option based on DDD (Domain-Driven Design) and Hexagonal principles:
\n\ncom.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\nNote: domain does not import anything from adapter. This is critical. If you see @Entity or @RestController in domain, the architecture is broken.
Example: Order Management System
\n\nLet'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\nStep 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