Modular Monolith

Updated Sep 23, 2026

A modular monolith is one deployable application organized into modules with explicit responsibilities, interfaces, and ownership. The modules run in the same process but limit how they use each other's code and data. In legacy modernization, it can be a target architecture or an intermediate step before selected capabilities become services.

A modular monolith keeps one application deployment while dividing the code into modules with clear responsibilities. Each module exposes a limited interface and keeps implementation details behind that interface. The structure makes dependencies visible without adding network calls or separate operational environments.

How module boundaries work

A module groups code and data rules for one coherent capability. Other modules use its published interface instead of importing internal classes or updating its records directly. The rules can be enforced through language visibility, build configuration, dependency tests, or repository structure.

Boundary concernWeak separationExplicit module rule
Code accessAny package imports any classOnly public interfaces are callable
Data accessModules update shared tables directlyOne module owns each write path
DependenciesCycles form between featuresDependency direction is checked
Change ownershipResponsibility follows foldersA team or role owns the capability

The application still starts, runs, and deploys as one unit. Modularity changes its internal design rather than its deployment topology.

Why it matters in a legacy codebase

A legacy monolith often contains useful boundaries that have become blurred over time. Refactoring can recover them incrementally. The team can move one capability behind an interface, redirect callers, and verify current behaviour before changing the next area.

An audit looks for evidence that suggests a viable module:

  • Cohesive behaviour. Rules and workflows that change for the same business reason.
  • Recognisable ownership. Data and operations that belong to one capability.
  • Limited dependencies. A manageable set of calls into and out of the proposed boundary.
  • Independent verification. Tests that can exercise the module through its interface.

These signals are more reliable than dividing code by technical layers alone. A user-interface folder, a service folder, and a data folder may all contain parts of the same capability.

Modular monolith versus separate services

A modular monolith uses in-process calls and one deployment. Separate services communicate across a network and can have independent releases. That adds operational and failure concerns, including timeouts, partial availability, and contract compatibility.

Service extraction may be justified when a capability needs independent scaling, deployment, technology, or ownership. Until then, an internal module can provide the same logical separation with fewer moving parts. A bounded context can guide either form because it defines a model boundary rather than a deployment choice.

Using it as a migration stage

The first stage is to map current dependencies and choose one boundary. The team then defines an interface, moves access behind it, and uses characterization tests to protect observable behaviour. Direct imports and data writes are removed after callers use the interface.

That sequence can leave the module inside the application or prepare it for later extraction. The target is not a particular number of services. It is a codebase where ownership, dependency direction, and change impact can be stated before the next refactoring step begins.