Skip to content

Introduction to Spring Boot | Advantage over Spring MVC and servlet based Web Applications

Introduction: The Story of Three Eras

Imagine a restaurant in its early days where the owner personally had to tell each waiter which table to serve, how to carry the plates, where the kitchen was, and how to write down orders. That is exhausting. Over time the restaurant hired a manager who handled most of that coordination. Eventually, the whole operation became so streamlined that new waiters just showed up and the system guided them. Each era solved the problems of the previous one.

That is exactly the evolution of Java web development: from raw Servlets, to Spring MVC, to Spring Boot. Understanding each era, what problems it solved and what problems it left behind, is the clearest path to truly understanding why Spring Boot is built the way it is.


Era 1: Servlets

In the early era of Java web development, Servlets were the foundation of every Java web application. A Servlet is a plain Java class that handles an HTTP request, processes it, and sends back a response.

What a Servlet Looks Like

java
@WebServlet("/demo-servlet-1")
public class DemoServlet1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // Check which endpoint was called
        String path = request.getPathInfo();
        if ("/first-endpoint".equals(path)) {
            // process and respond
            response.getWriter().write("Handled first endpoint");
        } else if ("/second-endpoint".equals(path)) {
            // process and respond
            response.getWriter().write("Handled second endpoint");
        }
        // ...more if-else for every endpoint
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // handle POST requests
    }
}

@WebServlet("/demo-servlet-2")
public class DemoServlet2 extends HttpServlet {
    // another Servlet with its own doGet, doPost, etc.
}

Notice that one Servlet can only have one doGet, one doPost, one doDelete. Every path variation within a Servlet had to be handled with if-else chains. For a production application with hundreds of endpoints, this became unreadable fast.

The Dreaded web.xml

Every Servlet needed a mapping in a file called web.xml. This XML file told the Servlet container (Tomcat) which Servlet class should handle which URL. For a large application with hundreds of Servlets, this file ballooned into a sprawling, hard to manage configuration nightmare.

xml
<web-app>
    <servlet>
        <servlet-name>DemoServlet1</servlet-name>
        <servlet-class>com.example.DemoServlet1</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DemoServlet1</servlet-name>
        <url-pattern>/demo-servlet-1</url-pattern>
    </servlet-mapping>
    <!-- repeated for every Servlet -->
</web-app>

How the Servlet Container Worked

Client Request
      |
      v
  Tomcat (Servlet Container)
      |
      | reads web.xml to find which Servlet handles this URL
      v
  DemoServlet1.doGet()  <-- actual business logic
      |
      v
  Response back to Client

The application had to be packaged as a WAR (Web Archive) file and deployed manually to Tomcat. Every code change meant rebuilding the WAR and redeploying.

Problems with Servlets

  1. web.xml grows unmanageable for large applications.
  2. One doGet per Servlet forces if-else path routing inside the method body.
  3. Tight coupling makes unit testing very hard. If class A creates an instance of class B with new B(), you cannot swap B for a mock in a test.
  4. Manual WAR packaging and deployment to an external server adds operational friction.

Era 2: Spring MVC — The Framework That Fixed Servlets

Spring Framework introduced a much more organised approach. It eliminated web.xml through annotation based configuration, introduced Inversion of Control (IoC) to solve tight coupling, and provided the DispatcherServlet to replace hand coded routing logic.

What Spring MVC Solved

Problem 1: Removal of web.xml

Spring replaced XML Servlet mappings with annotations:

java
@Controller
@RequestMapping("/payment")
public class PaymentController {

    @GetMapping("/get")
    public String getPayment() {
        return "payment details";
    }

    @PostMapping("/save")
    public String savePayment() {
        return "payment saved";
    }
}

Each method now has its own mapping annotation. One class can host many endpoints without a single line of XML.

Problem 2: Inversion of Control and Dependency Injection

This is the most important concept in the Spring ecosystem. Consider this tightly coupled code:

java
// PROBLEM: tight coupling
public class PaymentService {
    private UserService userService = new UserService(); // hard dependency

    public String getSenderDetails(String id) {
        return userService.getUserDetail(id); // always calls real UserService
    }
}

The new UserService() call creates a hard dependency. You cannot write a unit test for PaymentService.getSenderDetails() without also running real UserService code. There is no way to inject a mock.

Spring's solution:

java
@Component
public class UserService {
    public String getUserDetail(String id) {
        // fetch from DB
        return "User: " + id;
    }
}

@Component
public class PaymentService {

    @Autowired
    private UserService userService; // Spring injects this, not you

    public String getSenderDetails(String id) {
        return userService.getUserDetail(id);
    }
}

Now Spring (the IoC container) creates and manages the UserService object. In a unit test you can tell Spring to inject a mock UserService instead of the real one. The classes are now loosely coupled.

Problem 3: Organised REST API Handling

Instead of one doGet with a hundred if-else branches, each endpoint gets its own annotated method:

java
@Controller
@RequestMapping("/api")
public class SampleController {

    @GetMapping("/users")
    public String getUsers() {
        return "list of users";
    }

    @PostMapping("/users")
    public String createUser() {
        return "user created";
    }

    @GetMapping("/orders")
    public String getOrders() {
        return "list of orders";
    }
}

The DispatcherServlet: Spring's Traffic Controller

Spring MVC introduced the DispatcherServlet, a single front controller that receives every request and uses Handler Mappings (your annotations) to decide which controller class and which method to invoke.

Client Request
      |
      v
  Tomcat (Servlet Container)
      |
      v
  DispatcherServlet  <-- Spring's front controller
      |
      | uses Handler Mapping to find the right controller
      v
  PaymentController.getPayment()
      |
      | IoC creates the controller instance, resolves @Autowired dependencies
      v
  Response

Problems Still Remaining in Spring MVC

Even though Spring MVC was a massive improvement, setting it up was still quite verbose. A minimal Spring MVC project required:

java
// AppConfig.java — loads Spring MVC libraries, defines component scan
@Configuration
@EnableWebMvc
@ComponentScan("com.example")
public class AppConfig implements WebMvcConfigurer { }

// AppInitializer.java — registers and configures the DispatcherServlet
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() { return null; }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    @Override
    protected String[] getServletMappings() { return new String[]{"/"}; }
}

And pom.xml required manually managing versions for every dependency:

xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.10</version>  <!-- must pick compatible version -->
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>  <!-- must be compatible with spring-webmvc above -->
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>

If you upgraded one dependency, it might break compatibility with another. Tracking this across dozens of dependencies was tedious and error prone.


Era 3: Spring Boot — Convention Over Configuration

Spring Boot builds on top of Spring MVC and solves its remaining pain points. Every benefit Spring MVC has over Servlets is still present in Spring Boot. Spring Boot adds three more advantages on top.

Advantage 1: Dependency Management

Spring Boot introduces starter POMs. Instead of listing every dependency and its exact version, you declare a starter and Spring Boot pulls in all required dependencies at compatible versions automatically.

xml
<!-- Spring Boot: simple and safe -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.3</version>
</parent>

<dependencies>
    <!-- This one starter pulls in Spring MVC, Jackson, Tomcat — all compatible -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- no version needed — inherited from parent -->
    </dependency>

    <!-- This starter pulls in JUnit 5, Mockito — all compatible -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

You declare two starters and get perhaps 20 underlying dependencies, all version managed for you.

Advantage 2: Auto Configuration

Spring Boot automatically configures your application based on what is on the classpath. When it detects spring-boot-starter-web, it:

  • Registers a DispatcherServlet automatically
  • Sets up default component scanning from the package where your main class lives
  • Enables Spring MVC with sensible defaults

You do not write AppConfig.java. You do not write AppInitializer.java. You do not write @EnableWebMvc. Spring Boot has an opinionated view of how your application should be configured. If you agree with its opinion, you write zero configuration. If you need to override a default, you can, but overriding is the exception not the rule.

The one annotation that replaces all that configuration:

java
@SpringBootApplication
// This single annotation internally includes:
//   @EnableAutoConfiguration — triggers auto configuration
//   @ComponentScan — scans from this package downward
//   @Configuration — marks this as a configuration source
public class LearningSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(LearningSpringBootApplication.class, args);
    }
}

Advantage 3: Embedded Server

In Servlet and Spring MVC days, the application was packaged as a WAR file and deployed to an externally installed Tomcat server. In Spring Boot, Tomcat is embedded inside the application JAR itself.

Spring MVC:
  1. Write code
  2. Build WAR
  3. Install Tomcat separately
  4. Deploy WAR to Tomcat
  5. Start Tomcat

Spring Boot:
  1. Write code
  2. Run main()  <-- that's it, Tomcat starts inside the JVM

This means you can run a Spring Boot application anywhere Java is installed, with a single command:

bash
java -jar my-application.jar

No external server setup. No WAR files. The application is entirely self contained.

A Complete Minimal Spring Boot Application

java
// Main application class
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

// A controller
@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello from Spring Boot!";
    }
}

Run the main method. Open a browser. Go to http://localhost:8080/api/hello. See "Hello from Spring Boot!". That is all it takes.


How Spring Boot Handles a Request Internally

Even though Spring Boot hides the complexity, the same DispatcherServlet mechanism from Spring MVC still operates under the hood:

HTTP GET /api/hello
      |
      v
  Embedded Tomcat (started automatically)
      |
      v
  DispatcherServlet (registered automatically by Spring Boot)
      |
      | uses Handler Mapping to find @GetMapping("/hello")
      v
  MyController.hello()
      |
      | IoC resolved any @Autowired dependencies when the controller was created
      v
  "Hello from Spring Boot!" as response body
      |
      v
  HTTP 200 OK response to client

Comparison Table

FeatureServletsSpring MVCSpring Boot
URL Mappingweb.xmlAnnotationsAnnotations
Dependency InjectionNoYes (@Autowired)Yes (@Autowired)
Dependency Version ManagementManualManualAutomatic (starters)
Server SetupExternal Tomcat + WARExternal Tomcat + WAREmbedded (run JAR)
Configurationweb.xmlJava config classesAuto configured
Unit TestingVery hardPossibleEasy (mockable)
Lines of setup codeMediumManyMinimal

Summary

  • Servlets laid the foundation but required manual everything: web.xml, WAR files, and external Tomcat.
  • Spring MVC removed web.xml through annotations and introduced IoC/Dependency Injection, making code loosely coupled and testable. However it still required manual dependency version management and configuration boilerplate.
  • Spring Boot completes the picture with auto configuration, starter POMs for dependency management, and an embedded server. You focus on business logic, not plumbing.

Interview Questions

Q1. What is a Servlet and what is a Servlet container?

A Servlet is a Java class that handles an HTTP request, processes it, and returns a response. A Servlet container (like Tomcat) is the runtime environment that manages the lifecycle of Servlets: it receives incoming requests, maps them to the right Servlet using web.xml, and invokes the appropriate method (doGet, doPost, etc.).

Q2. What is the DispatcherServlet in Spring MVC?

The DispatcherServlet is the front controller in Spring MVC. It is the first point of contact for every HTTP request. It uses Handler Mappings (your @GetMapping, @PostMapping annotations) to determine which controller class and which method should handle the request. It then delegates to the IoC container to create controller instances and resolve their dependencies before invoking the target method.

Q3. What is Inversion of Control (IoC)?

IoC is a design principle where the control of object creation and lifecycle is transferred from the application code to a framework or container. Instead of writing new UserService() inside PaymentService, you let Spring create and manage the UserService object and inject it into PaymentService. This makes classes loosely coupled and easy to test.

Q4. What is Dependency Injection and how does it help with unit testing?

Dependency Injection (DI) is the implementation of IoC where a class receives its dependencies from outside rather than creating them internally. It helps with unit testing because you can inject mock objects in place of real dependencies. If PaymentService has a UserService injected by Spring, a unit test can inject a mock UserService and control its behavior without touching a database or network.

Q5. What are the three main advantages of Spring Boot over Spring MVC?

  1. Auto Configuration: No need to manually configure DispatcherServlet, AppConfig, or @ComponentScan. Spring Boot detects the classpath and configures automatically.
  2. Dependency Management: Starter POMs bundle compatible dependencies. No manual version management.
  3. Embedded Server: Tomcat runs inside the application JAR. No WAR packaging or external Tomcat installation required.

Q6. What is @SpringBootApplication?

It is a convenience annotation that combines three annotations:

  • @Configuration: marks the class as a source of bean definitions
  • @EnableAutoConfiguration: triggers Spring Boot's auto configuration mechanism
  • @ComponentScan: tells Spring to scan the current package and all sub packages for components

Q7. What is the difference between JAR and WAR packaging?

A JAR (Java Archive) is a standalone executable that contains the application code and an embedded server. It can be run directly with java -jar. A WAR (Web Archive) bundles the application for deployment to an external servlet container like Tomcat. In the microservices world, JAR is almost always the right choice because each service is a self contained, independently deployable application.

Q8. What does "convention over configuration" mean in Spring Boot?

It means Spring Boot provides sensible default configurations so you do not need to configure anything for the common case. For example, by convention it scans for components starting from the package of the main class, runs on port 8080, and uses HikariCP as the default connection pool. If you agree with these conventions you write no configuration. If you need to deviate, you can override each default individually.

Q9. What is tight coupling and why is it a problem?

Tight coupling occurs when one class directly creates or controls an instance of another class (e.g., new UserService()). It is a problem because it makes classes hard to test in isolation (you cannot inject mocks), hard to swap implementations, and hard to maintain as requirements change. Dependency Injection solves tight coupling by making the container responsible for wiring classes together.