A four‑month deep‑dive into the Spring ecosystem revealed the hidden complexity of a vanilla Spring MVC app. By building an API without Spring Boot, the author uncovered the dual‑context architecture, the pain of WAR‑based deployment, and the real value of starter dependencies—insights that clarify the trade‑offs between raw flexibility and the productivity gains of Boot.
Why Spring Boot Matters: Lessons from Building a Plain Spring MVC API
The problem – a Spring MVC app without any shortcuts
For months I was learning the Spring stack: design patterns, layered architectures, and the way large Java back‑ends are put together. I knew Spring Boot existed to auto‑configure everything, to ship an embedded server, and to bundle starter dependencies, but I never really understood why those conveniences were critical.
To force the issue I built a minimal Spring MVC service without Spring Boot. The goal was simple – expose a GET /hello endpoint – but the effort quickly exposed three hidden complexities:
- Two distinct application contexts – a Root context for shared beans and a Web context tied to each
DispatcherServlet. - Deployment friction – either package a WAR and push it to an external Tomcat, or embed Tomcat manually.
- Dependency hell – reconciling servlet API versions, Spring MVC, and Tomcat libraries without a starter.
Understanding these pain points is essential when evaluating whether the extra abstraction of Spring Boot is justified for a given project.
Solution approach – what the manual build taught me
1. Dual‑context architecture
Spring MVC separates concerns through two hierarchical ApplicationContexts:
- Root Application Context – holds services, repositories, data sources, and any infrastructure bean that must be shared across the whole JVM.
- Web Application Context – created per
DispatcherServlet. It owns controllers, view resolvers, exception handlers, and handler mappings.
Because each servlet gets its own child context, a single application can host multiple DispatcherServlets, each with a distinct set of MVC components while still sharing the root beans. This flexibility is powerful but also easy to misconfigure.
Spring Boot opinionates this by wiring a single
DispatcherServletand a unified context, which eliminates the need to manually link the two layers.
2. Deployment models
Without Boot I had two options:
| Model | Steps | Drawbacks |
|---|---|---|
| WAR + external Tomcat | mvn package → copy .war → restart Tomcat → check logs |
Slow feedback loop; any runtime error forces a full redeploy; version drift between Tomcat and libraries. |
| Embedded Tomcat | Add tomcat-embed-core and a main() that starts SpringApplicationBuilder |
Must manage server lifecycle yourself; still need correct servlet‑API versions; more boilerplate. |
The embedded approach felt like a tiny container‑as‑a‑library, but writing the glue code exposed the exact responsibilities Spring Boot hides.

3. Starter dependencies
Spring Boot starters are more than convenient pom bundles. They provide:
- Version alignment – a single BOM guarantees that
spring-webmvc,tomcat-embed, and related libraries are compatible. - Transitive dependency management – you avoid pulling in conflicting servlet API versions.
- Default auto‑configuration – the
spring-boot-starter-webautomatically registers aDispatcherServlet, configures Jackson, static resource handling, and health endpoints.
Manually replicating this required digging through the Spring reference docs, adding explicit @Configuration classes, and constantly updating version numbers.
Trade‑offs – when to keep Boot and when to go raw
| Aspect | Spring Boot | Plain Spring MVC |
|---|---|---|
| Developer velocity | Hot‑reload via DevTools, zero‑config server start‑up, opinionated defaults. | Every change forces a rebuild, redeploy, and often a server restart. |
| Flexibility | Limited to the conventions baked into starters; overriding defaults can be verbose. | Full control over servlet registration, multiple dispatcher servlets, custom lifecycle hooks. |
| Complexity surface | Hidden; easier for newcomers but can obscure why things behave a certain way. | Explicit; you see every bean, every filter, every version conflict. |
| Production footprint | Slightly larger jar due to embedded server, but consistent across environments. | Potentially smaller WAR if you already have a shared servlet container, but you must manage that container separately. |
| Learning curve | Shallow for simple services; deep when you need to break conventions. | Steep from day one; you learn the core Spring MVC model faster. |
In practice the decision often hinges on team size and product maturity. A startup that needs to iterate quickly will benefit from Boot’s rapid feedback loop. A large organization with strict runtime policies, or a microservice that must coexist with a legacy servlet container, may prefer the explicit control of a traditional WAR.
Takeaways for engineers
- App contexts are not a mystery – knowing the root vs. web hierarchy lets you design multi‑module services that share data sources without leaking MVC concerns.
- Embedded servers are a convenience, not a magic bullet – they shift the server lifecycle into your code, which is great for dev but still requires proper shutdown hooks for graceful termination.
- Starters are dependency guards – treat them as a curated BOM; rely on them unless you have a compelling reason to diverge.
- Experience the pain to value the abstraction – building a bare‑bones Spring MVC app is a worthwhile exercise for any engineer who plans to adopt Spring Boot in production.
Further reading
- Tomcat’s architecture overview explains the servlet container lifecycle: https://tomcat.apache.org/tomcat-5.5-doc/architecture/overview.html
- A concise walkthrough of Spring Boot’s auto‑configuration flow: https://medium.com/@dulanjayasandaruwan1998/understanding-spring-boot-architecture-flow-615d209b95f9


Comments
Please log in or register to join the discussion