Spring Modulith: Building Modular Monoliths with Spring Boot

Spring Modulith: The Best of Monoliths and Microservices

Spring Modulith modular monolith architecture bridges the gap between simple monoliths and complex microservices. Spring Modulith provides the tooling to enforce module boundaries, manage inter-module communication through events, and verify architectural rules at test time. Therefore, teams can start with a well-structured monolith and extract microservices only when specific modules need independent scaling.

The modular monolith approach acknowledges that microservices introduce significant operational complexity — distributed transactions, service discovery, network failures — that many applications don’t need. Moreover, Spring Modulith makes it easy to define clear module boundaries within a single deployable unit, getting the organizational benefits of modularity without the operational overhead of distributed systems. Consequently, development velocity remains high while the architecture stays clean and evolvable.

Spring Modulith Modular Monolith: Module Structure

Spring Modulith uses Java packages as module boundaries. Each top-level package under your application package becomes a module with controlled visibility. Furthermore, Spring Modulith verifies these boundaries at test time, catching violations before they reach production.

// Application structure — each package is a module
// com.myapp/
//   ├── order/          ← Order module
//   │   ├── Order.java  (public API)
//   │   ├── OrderService.java (public API)
//   │   └── internal/   ← Hidden from other modules
//   │       ├── OrderRepository.java
//   │       └── OrderValidator.java
//   ├── inventory/      ← Inventory module
//   │   ├── InventoryService.java
//   │   └── internal/
//   ├── payment/        ← Payment module
//   └── shipping/       ← Shipping module

// Order module — public API
package com.myapp.order;

@Service
@RequiredArgsConstructor
public class OrderService {
    private final ApplicationEventPublisher events;
    private final OrderRepository repository;

    @Transactional
    public Order createOrder(CreateOrderRequest request) {
        Order order = Order.create(request);
        repository.save(order);

        // Publish event — other modules react asynchronously
        events.publishEvent(new OrderCreated(
            order.getId(), order.getItems(), order.getTotal()
        ));
        return order;
    }
}

// Order events — part of the public API
public record OrderCreated(String orderId, List items, BigDecimal total) {}
public record OrderCancelled(String orderId, String reason) {}
Spring Modulith modular architecture code
Spring Modulith enforces module boundaries through package conventions and test-time verification

Event-Driven Communication Between Modules

Modules communicate through domain events rather than direct method calls. This decouples modules and makes the system more resilient — if the inventory module is temporarily broken, order creation still succeeds. Additionally, Spring Modulith provides an event publication log that ensures events are delivered even after application restarts.

// Inventory module — reacts to order events
package com.myapp.inventory;

@Service
@RequiredArgsConstructor
public class InventoryEventHandler {
    private final InventoryService inventoryService;

    @ApplicationModuleListener
    public void onOrderCreated(OrderCreated event) {
        for (OrderItem item : event.items()) {
            inventoryService.reserveStock(item.productId(), item.quantity());
        }
    }

    @ApplicationModuleListener
    public void onOrderCancelled(OrderCancelled event) {
        inventoryService.releaseReservation(event.orderId());
    }
}

// Payment module — reacts to order events
package com.myapp.payment;

@Service
public class PaymentEventHandler {
    @ApplicationModuleListener
    public void onOrderCreated(OrderCreated event) {
        paymentService.processPayment(event.orderId(), event.total());
    }
}

Architecture Verification Tests

Spring Modulith provides test infrastructure that verifies your modular architecture is correctly structured. These tests catch dependency violations, circular references, and improper access to internal packages. Furthermore, you can document your module structure automatically.

@SpringBootTest
class ModularityTests {

    @Test
    void verifyModularStructure() {
        ApplicationModules modules = ApplicationModules.of(Application.class);
        // Fails if any module accesses another module's internal package
        modules.verify();
    }

    @Test
    void documentModules() {
        ApplicationModules modules = ApplicationModules.of(Application.class);
        // Generates PlantUML and Asciidoc documentation
        new Documenter(modules)
            .writeModulesAsPlantUml()
            .writeIndividualModulesAsPlantUml();
    }

    @Test
    void verifyOrderModule() {
        ApplicationModules.of(Application.class)
            .getModuleByName("order")
            .ifPresent(module -> {
                // Verify order module only depends on allowed modules
                assertThat(module.getDependencies())
                    .extracting("name")
                    .containsOnly("inventory", "payment");
            });
    }
}
Architecture testing and verification
Architecture verification tests catch module boundary violations at build time

Migration Path to Microservices

When a module needs independent scaling, extract it into a microservice by replacing event publication with a message broker (Kafka, RabbitMQ) and converting the module’s public API to REST/gRPC endpoints. The clean module boundaries established by Spring Modulith make this extraction straightforward. See the Spring Modulith documentation for detailed patterns.

Key Takeaways

  • Start with a solid foundation and build incrementally based on your requirements
  • Test thoroughly in staging before deploying to production environments
  • Monitor performance metrics and iterate based on real-world data
  • Follow security best practices and keep dependencies up to date
  • Document architectural decisions for future team members
System architecture evolution planning
Spring Modulith provides a clear path from modular monolith to microservices when needed

In conclusion, Spring Modulith modular monolith architecture gives you the best of both worlds — the simplicity of a monolith with the organizational clarity of microservices. Start with a modular monolith, enforce boundaries with tests, communicate through events, and extract microservices only when you have a proven need for independent deployment or scaling.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top