Monolithic Architecture in System Design
A monolithic architecture builds an entire application as one unit: one codebase, one build, one deployable artifact and usually one database. The user interface, business rules and data access all live together and run in the same process. Most successful products, from small business tools to some of the busiest websites on the internet, began life as a monolith.
This tutorial explains what a monolith looks like inside, how a request flows through it, the different kinds of monoliths, how monoliths are deployed and scaled, their strengths and weaknesses, and how to build a well-structured modular monolith with Spring Boot. It ends with the warning signs that a monolith is being outgrown, common mistakes and best practices.
1. What Is Monolithic Architecture?
The word monolith means a single large block of stone. In software, a monolith is an application whose features are all built, tested and released together as a single piece.
Think of a traditional department store under one roof. Clothing, electronics, groceries and the billing counters are separate departments, but they share one building, one entrance, one electricity supply and one opening time. If the store renovates its electronics section, the whole store may have to close for the day. A monolithic application works the same way: features are organised into separate parts of the code, but they are packaged and run together.
Key characteristics:
- Single codebase: all features live in one repository and one project.
- Single deployable: the build produces one artifact, such as
shop-app.jar, that contains everything. - In-process communication: parts of the application call each other through normal method calls, not over the network.
- Shared database: usually one database holds the data for every feature.
- Deployed and scaled as a whole: to release one change or add capacity, you redeploy or copy the entire application.
2. Inside a Monolith: Layers and Modules
A monolith is not a big ball of random code. A well-built monolith has clear internal structure, usually organised in layers and, ideally, in business modules:
- Presentation layer: receives HTTP requests and returns HTML pages or JSON. In Spring Boot, these are
@Controllerand@RestControllerclasses. - Business (service) layer: contains the rules of the business, such as "an order cannot be placed for out-of-stock items". In Spring Boot, these are
@Serviceclasses. - Data access layer: reads and writes the database, typically through JPA repositories.
Layers organise code by technical role. Business modules organise it by feature: users, orders, inventory and payments. The best monoliths combine both, so each feature module has its own controllers, services and repositories, and modules talk to each other only through small public interfaces.
3. How a Request Flows Through a Monolith
Suppose a customer places an order on an online shop built as a monolith:
- The browser sends
POST /ordersto the server runningshop-app.jar. - The
OrderControllervalidates the input and callsOrderService.placeOrder(). OrderServicecallsInventoryService.reserve()andPaymentService.charge(). These are ordinary Java method calls inside the same process, taking microseconds.- All database changes (the order row, the stock update, the payment record) happen inside one database transaction. If the payment fails, everything is rolled back automatically.
- The controller returns
201 Createdwith the new order.
This simplicity is the monolith's superpower. There are no network failures between components, no message formats to agree on, and no partial updates to clean up, because the database guarantees all-or-nothing behaviour.
4. Types of Monoliths
Not all monoliths are equal. The difference between a pleasant monolith and a painful one is internal structure:
| Type | Description | Verdict |
|---|---|---|
| Traditional (layered) monolith | Organised only by technical layers; any class can call any other; one shared data model | Fine when small, becomes a "big ball of mud" as it grows |
| Modular monolith | Split into business modules with clear boundaries and public APIs, still deployed as one unit | An excellent default for most new products |
| Distributed monolith | Split into separately deployed services that still share a database or must be deployed together | An anti-pattern: network complexity without independence |
5. A Monolithic Spring Boot Application
Here is how a small online shop might be structured as a modular monolith. Packages are organised by business feature, and each module exposes only what other modules need:
shop-app/
├── pom.xml one build, one artifact: shop-app.jar
└── src/main/java/com/javabytechie/shop/
├── ShopApplication.java single entry point
├── orders/
│ ├── OrderController.java
│ ├── OrderService.java public API of the orders module
│ └── internal/ implementation details, not used by other modules
│ ├── Order.java
│ └── OrderRepository.java
├── inventory/
│ ├── InventoryService.java public API of the inventory module
│ └── internal/
└── payments/
├── PaymentService.java public API of the payments module
└── internal/
The order module coordinates the other modules with plain method calls, and Spring wraps the whole operation in one database transaction:
package com.javabytechie.shop.orders;
import java.math.BigDecimal;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.javabytechie.shop.inventory.InventoryService;
import com.javabytechie.shop.orders.internal.Order;
import com.javabytechie.shop.orders.internal.OrderRepository;
import com.javabytechie.shop.payments.PaymentService;
@Service
public class OrderService {
private final OrderRepository orders;
private final InventoryService inventory;
private final PaymentService payments;
public OrderService(OrderRepository orders, InventoryService inventory, PaymentService payments) {
this.orders = orders;
this.inventory = inventory;
this.payments = payments;
}
/**
* One method call, one transaction: if reserving stock or charging the card fails,
* every change made here is rolled back together.
*/
@Transactional
public Order placeOrder(long customerId, String sku, int quantity, BigDecimal unitPrice) {
Order order = orders.save(Order.create(customerId, sku, quantity, unitPrice));
inventory.reserve(sku, quantity); // in-process call, no network
payments.charge(customerId, unitPrice.multiply(BigDecimal.valueOf(quantity)), order.getId());
order.markConfirmed();
return order;
}
}
Notice what is missing compared with a microservices version: no HTTP clients, no retries, no timeouts, no message broker and no compensating actions. When the code fits in one process, the framework and the database handle consistency for you.
To keep module boundaries honest as the code grows, teams often add automated architecture tests (for example with ArchUnit) or use Spring Modulith, which can verify that modules only use each other's public APIs.
6. Building, Deploying and Scaling a Monolith
A monolith has a simple delivery pipeline: one build produces one artifact, which is tested and deployed as a whole. You can run it on a virtual machine, in a container or on a platform service.
To handle more traffic, you can scale it in two ways:
- Vertically: give the server more CPU and memory.
- Horizontally: run several identical copies behind a load balancer. This works well as long as the application is stateless, keeping sessions in a shared store rather than server memory.
The limitation is that you always scale everything. If only the search feature is busy, you still run extra copies of the payment and admin code. In practice this matters less than people think, because idle code costs little; the real bottleneck is usually the shared database, which you can relieve with caching, read replicas and good indexing. See Scalability for details.
7. Advantages of a Monolith
- Simple to develop: one project to open, one application to run locally, one place to search for code.
- Simple to test: end-to-end tests run against a single application without starting a fleet of services.
- Simple to deploy: one artifact, one pipeline, one version to track.
- Fast internal calls: method calls take nanoseconds to microseconds, compared with milliseconds for network calls.
- Strong consistency: one database transaction can cover any business operation.
- Easy debugging: one log stream and one stack trace show the whole story of a request.
- Lower operating cost: fewer servers, no service mesh, no distributed tracing infrastructure, a smaller operations team.
- Easy refactoring: the IDE can safely rename and move code across features, because it is all one codebase.
8. Disadvantages of a Monolith
- Growing complexity: without discipline, modules become entangled and every change risks breaking something unrelated.
- Slow builds and startups: very large codebases take longer to compile, test and start.
- Deployment coupling: a small fix in one feature requires redeploying the whole application, and one team's bug can block everyone's release.
- Coarse scaling: you cannot scale one busy feature independently.
- Shared failure: a memory leak or crash in one feature can take down the entire application.
- Technology lock-in: the whole application shares one language, framework and version; upgrading is an all-at-once project.
- Team scaling: when dozens of developers work in one codebase, coordination, merge conflicts and release scheduling become harder.
9. When a Monolith Is the Right Choice
A monolith, ideally a modular one, is usually the best starting point when:
- You are building a new product and the requirements and business boundaries are still changing.
- The team is small, from one person to a few teams that can coordinate releases easily.
- The application has moderate scale; a monolith with caching and replicas can serve millions of requests per day.
- You need strong consistency across features, such as in accounting or inventory systems.
- Speed of delivery and low operational cost matter more than independent deployments.
Many experienced architects recommend "monolith first": build a modular monolith, learn where the real boundaries are, and extract services only when a specific need appears. Compare the options in Monolithic vs Microservices Architecture.
10. Designing a Good Modular Monolith
A modular monolith gives you most of the organisational benefits of microservices while keeping the simplicity of one deployable. These rules keep it healthy:
- Package by feature, not by layer. Put everything for orders in an
orderspackage instead of spreading it across globalcontrollers,servicesandrepositoriespackages. - Expose a small public API per module. Other modules call
InventoryService, never the inventory repository or entities directly. - Own your tables. Each module reads and writes only its own tables. If orders need product names, they ask the catalogue module or keep their own copy.
- Prefer events for side effects. When an order is placed, publish an
OrderPlacedevent; the email and loyalty modules react to it instead of being called directly. - Enforce boundaries automatically with architecture tests in the build, so violations are caught in code review, not years later.
Spring makes in-process events easy:
package com.javabytechie.shop.orders;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
// Public event of the orders module: other modules depend on this, not on order internals
public record OrderPlacedEvent(long orderId, long customerId, String email) { }
@Component
class OrderEvents {
private final ApplicationEventPublisher publisher;
OrderEvents(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
void orderPlaced(long orderId, long customerId, String email) {
publisher.publishEvent(new OrderPlacedEvent(orderId, customerId, email));
}
}
// In the notifications module: runs only after the order transaction commits successfully
@Component
class OrderConfirmationEmailer {
@TransactionalEventListener
void on(OrderPlacedEvent event) {
System.out.println("Sending confirmation email for order " + event.orderId() + " to " + event.email());
}
}
Because modules already communicate through public APIs and events, extracting one into a separate service later is far easier: the event becomes a message on a broker, and the method call becomes an HTTP or gRPC call.
11. Signs You Are Outgrowing Your Monolith
- Teams regularly wait for each other to release, and deployments happen less often because they are risky.
- Builds and test runs take so long that developers lose focus waiting for them.
- One part of the system has very different needs, for example heavy CPU work for video processing, or far higher traffic than everything else.
- A failure in a non-critical feature keeps bringing down critical ones such as checkout.
- Different parts of the system genuinely need different technologies or release cycles.
Even then, the first step is often to improve modularity inside the monolith. Extract a service only for the specific module that needs independence, using the strangler fig approach described in the comparison tutorial.
12. Real-World Examples
- Start-up products: most begin as a single Rails, Django, Laravel or Spring Boot application, because it is the fastest way to reach customers.
- Large modular monoliths: some very large e-commerce and developer platforms have publicly described running a single, carefully modularised codebase that serves enormous traffic, investing in module boundaries instead of splitting into hundreds of services.
- Lean, high-traffic sites: some well-known question-and-answer and community sites have published that they serve very large audiences from a monolithic application on a small number of powerful servers, with heavy caching.
- Internal business systems: HR, accounting, inventory and admin tools are frequently, and sensibly, monoliths.
13. Common Mistakes to Avoid
- No internal boundaries: letting any class use any other class, until nothing can be changed safely.
- Shared "god" entities: one huge
CustomerorOrderclass used by every feature, with fields for everyone's needs. - Storing session state in memory: this prevents running multiple copies behind a load balancer.
- Neglecting the database: missing indexes, no caching and no read replicas make the monolith look slow when the real problem is data access.
- Splitting too early: moving to microservices because it is fashionable, before the team or the product needs it.
- Creating a distributed monolith: splitting code into services that still share a database or must be released together.
14. Best Practices
- Start with a modular monolith organised by business feature.
- Give each module a clear owner, a small public API and its own database tables.
- Enforce module boundaries with automated architecture tests.
- Keep the application stateless so it can run as several copies behind a load balancer.
- Invest in fast builds, a good automated test suite and continuous delivery, so deployments stay small and frequent.
- Use caching, read replicas and indexing before considering a split for performance reasons.
- Use feature flags to release incomplete work safely from a single codebase.
- Monitor per-module metrics, so you know which part would benefit from extraction if the need arises.
15. Summary
- A monolithic architecture builds, deploys and scales an application as one unit with a shared codebase and usually one database.
- Internally it is organised in layers and, ideally, in business modules with clear boundaries.
- Monoliths are simple to build, test, deploy and debug, with fast in-process calls and strong transactional consistency.
- Their weaknesses appear at scale: entangled code, slow builds, coupled deployments and coarse scaling.
- A well-designed modular monolith is an excellent default, and makes extracting services later much easier.
A monolith is not a legacy mistake; it is often the most productive architecture you can choose. Open the FAQ tab for answers to common questions about monolithic architecture.
Frequently Asked Questions (FAQ)
Q1. What is monolithic architecture in simple words?
It is a way of building an application as one single unit: all features share one codebase, are built into one deployable package and usually use one database. To change or scale any part, you redeploy or copy the whole application.
Q2. What are the main components of a monolithic application?
A typical monolith has a presentation layer (web pages or REST controllers), a business layer (services containing business rules) and a data access layer (repositories), all running in one process and connected to a shared database.
Q3. What are the advantages of monolithic architecture?
- Simple development, testing and deployment.
- Fast in-process communication.
- Easy transactions and strong consistency.
- Straightforward debugging and monitoring.
- Lower infrastructure and operational cost.
Q4. What are the disadvantages of monolithic architecture?
As a monolith grows, code can become tangled, builds slow down, every change requires redeploying everything, features cannot be scaled independently, a failure in one part can crash the whole application, and many teams in one codebase must coordinate releases.
Q5. What is a modular monolith?
A modular monolith is a single deployable application divided into well-defined business modules, each with its own code, public interface and data. Modules communicate only through those interfaces or events. It keeps the simplicity of a monolith while gaining much of the organisation of microservices.
Q6. What is a distributed monolith?
A distributed monolith is a system split into separately deployed services that remain tightly coupled, for example through a shared database, synchronous call chains or the need to deploy together. It suffers the complexity of a distributed system without the independence benefits, so it is considered an anti-pattern.
Q7. Is a monolith bad?
No. A monolith is often the best choice for new products, small teams and systems that need strong consistency. Problems come from poor internal structure, not from being a monolith. A well-organised modular monolith can serve very large traffic.
Q8. Can a monolithic application scale?
Yes. It can scale vertically on bigger machines and horizontally by running several identical copies behind a load balancer, provided it is stateless. The shared database is usually the main bottleneck, addressed with caching, read replicas and indexing.
Q9. Is Spring Boot a monolithic framework?
Spring Boot is a framework, not an architecture. You can build a monolith with Spring Boot, packaging all features into one application, or build microservices where each service is its own Spring Boot application. Both are common.
Q10. What is the difference between layered architecture and monolithic architecture?
Monolithic describes how an application is deployed: as one unit. Layered describes how code is organised inside: by technical layers such as presentation, business and data access. Most monoliths are layered, but a monolith can also be organised by business modules.
Q11. How do you deploy a monolithic application?
Build one artifact, such as an executable JAR or a container image, run automated tests on it, and deploy it to servers, containers or a platform service. For zero-downtime releases, use rolling, blue-green or canary deployments behind a load balancer.
Q12. What database does a monolith use?
Usually one relational database such as MySQL or PostgreSQL shared by all features, which makes transactions and joins easy. In a modular monolith, each module should still own its own set of tables, even within the same database.
Q13. Why do startups prefer monolithic architecture?
Because it lets a small team build and change features quickly with minimal infrastructure. Startups need to learn what customers want, and a single codebase makes rapid change, testing and deployment much easier than coordinating many services.
Q14. When should you move from a monolith to microservices?
Consider it when many teams are blocked by shared releases, a specific part needs independent scaling or technology, failures in minor features keep affecting critical ones, or builds and deployments have become too slow, and after improving modularity inside the monolith has not solved the problem.
Q15. What is the "monolith first" approach?
It is the strategy of starting with a well-structured monolith, learning the real business boundaries as the product evolves, and only extracting microservices when a clear need appears. It avoids paying the cost of distribution before it delivers value.
Q16. How do modules communicate in a monolith?
Through in-process method calls on public interfaces, or through in-process events such as Spring application events. Good modular monoliths forbid modules from accessing each other's internal classes or database tables directly.
Q17. How do you keep a monolith maintainable?
Organise code by business feature, keep public module APIs small, give modules their own tables, enforce boundaries with architecture tests, maintain fast automated tests, refactor continuously and keep deployments small and frequent.
Q18. Do monoliths have a single point of failure?
A single running instance does. Running several instances behind a load balancer, across availability zones, with a replicated database removes most single points of failure, although a bug that crashes the application can still affect every instance.
Q19. Are monoliths still used by large companies?
Yes. Many large companies run important systems as monoliths or modular monoliths, and some have publicly described serving very high traffic this way. Others use a mix: a core monolith plus a few services extracted where independence was genuinely needed.
Q20. How should I explain monolithic architecture in an interview?
Define it as a single deployable unit with a shared codebase and database, describe its layers, list advantages (simplicity, speed, consistency) and disadvantages (coupling, coarse scaling, deployment risk), mention the modular monolith as a strong default, and explain when you would extract services.