- Early way of handling web requests.
- Each request starts a new process ➞ very slow.
- Example: Perl/Python/Java program invoked per request.
- Dependencies: None, OS-level executables.
- Embeds Java code inside HTML.
- Faster than CGI, but mixes business logic with presentation ➞ messy.
- Dependencies:
javax.servlet.jsp-api(container-provided), JSP compiler.
- Pure Java classes handling HTTP requests/responses.
- First introduced in late 1990s with Java Servlet API 2.1 (1997).
- Portable across servers supporting the Servlet API:
- Apache Tomcat
- Jetty
- WildFly / JBoss EAP
- GlassFish / Payara
- IBM WebSphere
- Oracle WebLogic
- Clear separation from HTML, but too low-level.
- Dependencies:
javax.servlet-api, servlet container (Tomcat, Jetty).
MyApp/
├── src/
│ └── com/example/HelloServlet.java
├── WebContent/
│ ├── index.html
│ └── WEB-INF/
│ └── web.xml
HelloServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.println("Hello, World (old style)!");
}
}web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.example.HelloworldController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>build.gradle Only compile time dependency. Runtime implementation provided by tomcat
dependencies {
compileOnly 'jakarta.servlet:jakarta.servlet-api:5.0.0' // Or a newer version
}MyApp/
├── src/
│ └── com/example/HelloServlet.java
├── WebContent/
│ └── WEB-INF/
│ └── web.xml (optional/empty)
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.println("Hello, World (new style)!");
}
}Requirements:
- GET
/discount?type=VIP→ returns discount percentage. - GET
/discount/history→ returns last 5 discounts. - Discounts are calculated using business logic (service layer).
- Output is JSON.
- Needs proper error handling.
- Needs to be testable without running a server.
You could do it with two Servlets:
@WebServlet("/discount")
public class DiscountServlet extends HttpServlet {
private final DiscountService service = new DiscountService();
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
try {
String type = req.getParameter("type");
double discount = service.calculateDiscount(type);
// Manual JSON
res.setContentType("application/json");
res.getWriter().println("{\"discount\":" + discount + "}");
} catch(Exception e) {
res.setStatus(500);
res.getWriter().println("{\"error\":\"Something went wrong\"}");
}
}
}
@WebServlet("/discount/history")
public class DiscountHistoryServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
// Manual JSON for list
res.setContentType("application/json");
res.getWriter().println("[5, 10, 15, 20]");
}
}Problems here:
- Each URL requires a separate Servlet.
- Manual JSON serialization.
- Manual error handling.
- Hard to inject services or reuse code across Servlets.
- Testing requires a full Servlet container or complex mocks.
Now with Spring MVC, we can do everything in one controller, with less boilerplate:
@RestController
@RequestMapping("/discount")
public class DiscountController {
private final DiscountService service;
public DiscountController(DiscountService service) {
this.service = service;
}
@GetMapping
public Map<String, Double> getDiscount(@RequestParam String type) {
double discount = service.calculateDiscount(type);
return Map.of("discount", discount); // automatic JSON serialization
}
@GetMapping("/history")
public List<Integer> getHistory() {
return List.of(5, 10, 15, 20); // automatic JSON
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleError(Exception e) {
return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
}
}Advantages:
- One class handles multiple URLs (
/discountand/discount/history) with clear annotations. - Automatic JSON serialization → no manual string building.
- Dependency injection → no
new DiscountService()inside the controller. - Centralized error handling using
@ExceptionHandler. - Easier testing → we can test this controller with
MockMvcwithout a Servlet container.
| Feature | Servlet | Spring MVC |
|---|---|---|
| URL mapping | Manual, one servlet per URL | Annotation-based, multiple methods per controller |
| JSON | Manual serialization | Automatic with Jackson |
| Error handling | Manual | Centralized with @ExceptionHandler |
| Dependency injection | Manual | @Autowired or constructor injection |
| Testability | Hard | Easy with MockMvc |
| Scalability | Many servlets, lots of boilerplate | One controller handles multiple endpoints cleanly |
✅ Bottom line:
Yes, you can achieve separation and mapping manually with Servlets, but Spring MVC automates repetitive tasks, reduces boilerplate, and integrates dependency injection, validation, error handling, and testing. This makes building and maintaining complex apps feasible.
MyApp/
├── src/
│ └── com/example/controller/HelloController.java
├── WebContent/
│ ├── WEB-INF/
│ │ ├── web.xml
│ │ └── dispatcher-servlet.xml
HelloController.java
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello, World from Spring MVC (XML)!";
}
}web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="3.1">
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>dispatcher-servlet.xml
Note on dispatcher-servlet.xml: Even though web.xml does not explicitly reference dispatcher-servlet.xml, Spring automatically looks for a file named -servlet.xml in WEB-INF/ when initializing the DispatcherServlet. In our case, the servlet name is dispatcher, so Spring loads WEB-INF/dispatcher-servlet.xml automatically.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.example.controller"/>
<mvc:annotation-driven/>
</beans>- Dependencies:
spring-corespring-contextspring-webspring-webmvcjackson-databind
MyApp/
├── src/
│ └── com/example/
│ ├── AppInitializer.java
│ ├── WebConfig.java
│ └── controller/HelloController.java
HelloController.java
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello, World from Spring MVC (No XML)!";
}
}WebConfig.java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.controller")
public class WebConfig {
// Extra MVC configuration if needed
}AppInitializer.java
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}- Dependencies: Same as XML-based Spring MVC.
- DispatcherServlet ➞ Front Controller in Spring MVC.
- Maps incoming requests to controllers via annotations (
@RequestMapping). - Normally, controllers return view names (JSPs, Thymeleaf, etc.) ➞ ViewResolver maps them.
- With
@ResponseBody, view resolution is skipped ➞ return value is written directly to HTTP response body (JSON/String).
- Normally, a method in a
@Controllerreturns a view name. - With
@ResponseBody, Spring writes the return value directly to the HTTP response. - Used for APIs returning JSON or plain text instead of HTML views.
Example:
@RequestMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello";
}-
Servlet API Standard
Tomcat supportsServletContainerInitializer(from Servlet 3.0 spec). -
Spring’s META-INF/services mechanism
- Spring provides file:
META-INF/services/javax.servlet.ServletContainerInitializer. - It lists the class:
org.springframework.web.SpringServletContainerInitializer. - Tomcat reads this file at startup.
SpringServletContainerInitializerimplementsServletContainerInitializer.- Since
ServletContainerInitializeris part of Servlet spec, Tomcat knows to call it.
- Spring provides file:
-
SpringServletContainerInitializer Delegation
- Delegates to classes implementing
WebApplicationInitializer. - Framework developers or users implement
WebApplicationInitializer.
- Delegates to classes implementing
-
AbstractAnnotationConfigDispatcherServletInitializer
- A convenience base class for
WebApplicationInitializer. - Registers one DispatcherServlet per implementation.
- You can create multiple implementations if you want multiple
DispatcherServlets.
- A convenience base class for
-
DispatcherServlet Creation
- Registers
DispatcherServletwith mappings (/or custom). - Initializes Spring context (
WebConfigetc.).
- Registers
This explains step by step how Spring MVC initializes within a servlet container like Tomcat.
- Starting from Servlet 3.0, servlet containers like Tomcat introduced a mechanism called
ServletContainerInitializer. - This is a hook: the container says, “If someone wants to run code when the server starts, I’ll call this interface for them.”
-
Spring creates the file:
META-INF/services/javax.servlet.ServletContainerInitializer -
Inside it lists the class:
org.springframework.web.SpringServletContainerInitializer -
Tomcat reads this file automatically at startup and calls Spring’s initializer.
Think of it as:
Tomcat says: “Who wants to do something at startup?” Spring says: “We do!” Tomcat calls Spring’s initializer.
SpringServletContainerInitializeritself doesn’t configure your app.- It looks for classes that implement
WebApplicationInitializer. - Your custom initialization logic is handled by these classes.
-
Spring provides this base class to simplify
WebApplicationInitializer. -
What it does:
- Registers DispatcherServlet.
- Loads Spring context (
@Configurationclasses). - Maps URLs (default
/).
Think of it as: A helper that wires everything automatically.
-
DispatcherServlet = Spring MVC front controller.
-
On startup:
- Registered with the servlet container.
- URL mappings set up (
/or custom). - Spring context loads controllers,
@RequestMapping, and beans.
Tomcat Startup
|
v
ServletContainerInitializer (Servlet 3.0 Spec)
|
v
SpringServletContainerInitializer (Spring)
|
v
WebApplicationInitializer (User / AbstractAnnotationConfigDispatcherServletInitializer)
|
v
Registers DispatcherServlet + Loads Spring Context
|
v
Controllers (@Controller) and RequestMappings (@RequestMapping) ready
|
v
HTTP Requests --> DispatcherServlet --> Controllers --> Response
- Tomcat provides startup hook (
ServletContainerInitializer). - Spring registers its initializer (
SpringServletContainerInitializer). - Spring finds your
WebApplicationInitializer. - DispatcherServlet is registered and Spring context is loaded.
- Spring MVC is ready to handle requests.
- Marks class as a source of Spring bean definitions.
- Equivalent to XML
<beans>...</beans>. - Without
@Configuration,@Beanmethods won’t be processed properly. - If replaced with
@Component, the class is still picked up, but beans defined via@Beaninside it won’t get proper lifecycle management.
- Switches on default Spring MVC configuration.
- Equivalent to
<mvc:annotation-driven/>. - Registers essential beans:
RequestMappingHandlerMapping➞ maps URLs to@RequestMappingmethods.RequestMappingHandlerAdapter➞ invokes those methods.HttpMessageConverters➞ handle JSON/XML conversion (via Jackson).- Default config for validation, formatting, etc.
Without @EnableWebMvc Example:
@Configuration
@ComponentScan("com.example.controller")
public class WebConfig { }→ Your controllers exist, but Spring won’t map them properly → you’ll get 404 errors.
With @EnableWebMvc Example:
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig { }→ Spring wires controllers, mappings, and JSON serialization.