Explain and quantify the four TOC advantages of monoliths, recognise contexts where they outweigh distribution, exploit them deliberately (single pipeline, local transactions, in-process testing and debugging), avoid undermining them with premature distribution, and argue for a modular monolith in architecture reviews
A monolith's advantages flow from having one codebase, one deployable, one process and one database: fewer moving parts, one release, requests that can be followed in one process, and business operations that commit atomically.
Figure 1 — Advantages of a monolith (TOC)
TOC advantage
Definition
Simplicity
Fewer components, technologies and failure modes to build, test, secure and operate; one mental model of the system
Deployment
One artefact and one pipeline; releases need no cross-service version coordination or contract drift management
Debugging
A request executes in one process: one stack trace, one log stream, breakpoints and profilers see everything
Transactions
Business operations spanning modules commit or roll back atomically in one local ACID transaction
Table 1 — The four TOC advantages
2Why It Exists
Industry enthusiasm for microservices led many teams to pay distribution costs they didn't need. Understanding monolith advantages precisely lets architects defend simpler designs where they fit — often the majority of systems.
Microservices solve real problems, but mainly organisational ones: many teams that need to deploy independently, and parts of a system with very different scaling or availability needs. Most systems, especially young ones, have neither. They have a small number of teams, an uncertain domain and a pressing need to ship. For them each of the four advantages is worth a lot: one pipeline instead of dozens, a stack trace instead of distributed tracing, an ACID transaction instead of a saga, and a security surface of one application. Knowing these advantages precisely also gives architects a vocabulary for reviews. Instead of defending a monolith as merely familiar, they can state exactly which costs a split would add and which driver would justify paying them.
Figure 6 — Transactions (TOC): ACID across modules
JAVA
@Transactionalpublic OrderId placeOrder(PlaceOrder cmd) { inventory.reserve(cmd.lines()); // module A (same DB) Order order = orders.save(Order.place(cmd)); // module B loyalty.awardPending(cmd.customerId(), order.total()); // module Creturn order.id(); // all commit together or none}
4How It Works Internally — Why the Advantages Hold
In-process events with Modulith registry (still one tx boundary)
Some isolation
Separate executors/pools, not separate services
Table 5 — Scale the monolith before splitting it
JAVA
// Module-slice test: fast test of one module, still in one JVM@ApplicationModuleTest(mode = BootstrapMode.DIRECT_DEPENDENCIES)class PricingModuleTest { … }
5APIs / Features / Syntax
Feature
Advantage served
@Transactional (Spring)
Transactions
Spring Boot executable JAR / Buildpacks
Deployment
Testcontainers + @SpringBootTest
Full-system tests
JFR, IntelliJ debugger
Debugging
Spring Modulith events
Decoupling without distribution
Actuator
Operational simplicity
Table 6 — Toolkit
6Practical Examples
6.1 Startup chose monolith, won the race
A fintech startup launched with a modular Spring monolith and 8 engineers, shipping to market in five months. A competitor started with 20 microservices and took 14 months. The monolith later extracted two services at 60 engineers.
6.2 Transaction simplicity
A distributed implementation of order + stock + loyalty needed a saga with compensations and 1,200 lines of failure handling. The monolith version was one @Transactional method and 80 lines.
@SpringBootTest@Testcontainersclass CheckoutFlowTest {@Container@ServiceConnectionstatic PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:17");@Testvoid placesOrderEndToEnd() { … } // whole system in one JVM}
6.7 Worked example — consistency without sagas
Invariant
Monolith implementation
Stock never negative
Row lock + check in same tx as order
Table 10 — Strong consistency for free
6.8 Worked example — refactoring across modules
Change
Monolith
Microservices
Rename a concept everywhere
IDE refactor, one PR
Versioned contracts, many PRs
Table 11 — Cross-cutting refactors are easy
6.9 Worked example — onboarding
Monolith
Microservices
Clone one repo, run one app
Clone many, run many or use shared env
Table 12 — Simpler developer experience
6.10 Worked example — security surface
Surface
Monolith
External endpoints
One app behind gateway
Internal network calls
None
Table 13 — Fewer attack paths
6.11 Worked example — performance
Operation
Monolith latency
Cross-module call
µs
Checkout p99
120 ms
Table 14 — No network overhead
6.12 Worked example — observability needs
Need
Monolith
Tracing
Useful, not mandatory
Metrics
Per-module via Micrometer
Table 15 — Lower observability tax
6.13 Worked example — decision matrix
Driver
Monolith advantage relevant?
Time to market
✔ strongly
Strict consistency
✔
Independent scaling
✘
Many autonomous teams
✘
Table 16 — Match advantages to drivers
6.14 Worked example — advantages lost by distribution
Split
Advantages lost
Orders ↔ Inventory
Transactions, debugging simplicity
Table 17 — Know what you give up
6.15 Worked example — well-known modular monoliths
Undermining advantages with premature distribution.
Letting pipelines slow until deployment becomes painful.
Letting modules erode, turning advantages into mud.
Using distributed patterns (sagas) inside one DB unnecessarily.
No documented revisit criteria.
These mistakes have one thing in common: the advantages of a monolith are treated as automatic, so nobody protects them. Teams dismiss the design as legacy and split it before the domain is understood, giving up transactions and debugging simplicity for no measured gain. Others keep the monolith but let it decay: builds take forty minutes, so deployments become weekly events, and module boundaries erode until a change in pricing breaks checkout. A subtler error is copying distributed patterns into one database, such as sagas and compensations between modules that could share a local transaction. Finally, without revisit criteria in an ADR, such as team size or divergent scaling needs, the decision to stay or split is made by fashion rather than evidence.
8Performance & Production Considerations
Aspect
Guideline
Simplicity
Few components, one data store
Deployment
Fast pipeline, rolling/blue-green
Debugging
Per-module metrics, logs, profiling
Transactions
Local ACID for cross-module invariants
Protection
Modulith verify, test slicing
Table 24 — Operational guidelines
9Architecture & Design Considerations
Treat monolith advantages as assets worth defending.
Choose distribution only when drivers outweigh these advantages.
Scale the monolith (caching, replicas, isolation) before splitting.
Record revisit criteria in an ADR.
10Interview Questions & Answers
Q1BasicConcept
What are the main advantages of a monolith?
The four main advantages are simplicity, easy deployment, easier debugging and local ACID transactions. Simplicity means one codebase, one process and one database, so there are fewer components, technologies and failure modes to build, secure and operate. Deployment is one artefact through one pipeline, with no cross-service version coordination or contract drift. Debugging is easier because a request runs in one process with a complete stack trace and one log stream. Transactions spanning modules commit or roll back together. These advantages are not trivial: they translate into faster time to market, lower infrastructure cost and shorter incident diagnosis, and every move to distribution should be justified by drivers that outweigh losing them.
Q2BasicConcept
Why is debugging easier in a monolith?
Because the whole request executes in one process, so every tool sees all of it. The IDE debugger can step from the controller through pricing, inventory and order code without crossing a network. A failure produces one complete stack trace showing the full call chain, and logs arrive in one stream with the same thread and correlation context. JFR or async-profiler can profile the entire request, and a Testcontainers-based full-system test reproduces it in one JVM. In microservices the same request spans several processes and logs, often with partial failures, so you need distributed tracing just to find which service failed. The note's example puts median root-cause time at 25 minutes versus three hours.
Q3IntermediateTrade-off
How do transactions differ between monolith and microservices?
In a monolith a business operation spanning modules runs in one local ACID transaction on one database. With Spring's @Transactional, reserving stock, saving the order and awarding loyalty points share one connection and one transaction manager, so they all commit or all roll back, and invariants such as stock never going negative can be enforced with a row lock. In microservices each service owns its database, so there is no shared transaction. You need a saga with compensating actions, an outbox for reliable events, idempotent handlers and acceptance of visible intermediate states. The note's example shows the gap: one 80-line method versus a saga with 1,200 lines of failure handling.
Q4IntermediateConcept
What is "MonolithFirst"?
MonolithFirst is Martin Fowler's heuristic that most new systems should start as a monolith, preferably a modular one, and split only when the need is proven. The reasoning is that early on you do not know the domain well enough to draw stable service boundaries, and wrong boundaries are far more expensive to fix across services than across modules. Starting with a modular monolith gives you speed, simple operations and local transactions while you learn. When boundaries have stabilised and a real driver appears, such as many teams or divergent scaling, you extract the module that needs it. The note's fintech example extracted its first two services only at 60 engineers.
Q5Senior EngineerDesign
How do you keep deployment simple as teams grow?
Protect the single pipeline so it stays fast and safe as more people commit. Trunk-based development with short-lived branches avoids long merges, and feature flags let teams merge unfinished work without releasing it. Build time is the main threat, so use modular builds, a build cache, parallel test execution and module-scoped tests on pull requests, with the full suite on main; the note shows these cutting pipeline time by 40 to 70 percent. Enforce module boundaries with Spring Modulith verification so one team's change does not break another's. Keep releases rolling or blue-green on Kubernetes with readiness probes, and keep rollback as simple as redeploying the previous image.
Q6Senior EngineerInternals
How can you get asynchronous decoupling without distribution?
Use in-process application events with Spring Modulith's event publication registry. A module publishes a domain event such as OrderConfirmed inside its transaction, and listeners annotated with @ApplicationModuleListener run after commit, asynchronously and in their own transaction. The registry stores each publication in a database table in the same transaction as the business change, marks it complete when the listener succeeds, and lets incomplete ones be resubmitted after a failure or restart. That gives you the decoupling of events, with modules that do not call each other directly and side effects such as notifications off the request path, without a broker, network calls or a second deployable. The events can later be externalised to Kafka.
Q7Technical ArchitectScenario
What should you try before splitting a monolith for scale?
Try the cheaper scaling options first, because most hot spots can be fixed inside the monolith. Start by measuring which module is hot and why, using per-module Micrometer metrics and profiling. Then optimise that module's queries and indexes, add caching for read-heavy data, and route reporting or listing reads to a read replica. Scale horizontally by running more instances behind the load balancer, which works as long as the application is stateless. Isolate noisy work with separate executors, thread pools or bulkheads, and move background jobs to a separate deployment of the same artefact if needed. Only if one module still needs very different scaling or availability is extraction justified.
Q8Solution ArchitectTrade-off
How would you argue for a monolith in an architecture review?
I would argue from drivers, not taste. First I list the business drivers, such as time to market, strict consistency, team size and expected load, and map each to the four advantages: simplicity, single-pipeline deployment, debugging and local transactions. Then I quantify what distribution would cost: extra infrastructure, a platform with mesh, broker and tracing, sagas for cross-module invariants and slower incident diagnosis. I propose a modular monolith with enforced boundaries, Spring Modulith verification and in-process events, so a later split stays cheap. Finally I record explicit revisit criteria in the ADR, for example more than 30 engineers or divergent scaling, so reviewers see a deliberate, reversible choice.
Newsletter
Get new tutorials every week
New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.
Your subscription could not be saved. Please try again.
Almost done! Check your inbox and click the confirmation link to complete your subscription.