Core Mechanisms of Servlet Request and Response Objects
In the Java Servlet ecosystem, the container bridges raw HTTP protocol traffic and business logic through two fundamental abstractions: HttpServletRequest and HttpServletResponse. The former encapsulates incoming client data, while the latter orchestrates the server's reply. Understanding their lifecycle, data extraction methods, and encoding behaviors is essential for building reliable web applications.
- HttpServletRequest: Extracting Client Input
1.1 Inheritance Hierarchy
| Component | Description |
|---|---|
ServletRequest |
Protocol-agnostic root interface defined by the Java EE specification. |
↑ |
-- |
HttpServletRequest |
HTTP-specific extension providing methods for headers, methods, and query strings. |
↑ |
-- |
Container Implementation (e.g., RequestFacade) |
Tomcat's internal class that wraps the actual socket data and exposes it via the API. |
1.2 Parsing Request Metadata
The HTTP request consists of three segments: the start line, headers, and payload body. The servlet API provides dedicated methods to access each segment.
Start Line
// Example: GET /app/users?id=42 HTTP/1.1
String method = req.getMethod(); // "GET"
String context = req.getContextPath(); // "/app"
String uri = req.getRequestURI(); // "/app/users"
String query = req.getQueryString(); // "id=42"
Headers
String userAgent = req.getHeader("User-Agent");
Enumeration<String> headerNames = req.getHeaderNames();
Body & Parameters
While getInputStream() and getReader() provide raw access to the payload, the container automatically parses form data and query strings into convenient parameter maps.
// Single value
String clientId = req.getParameter("clientId");
// Multiple values (e.g., checkboxes)
String[] roles = req.getParameterValues("roles");
// Complete dataset
Map<String, String[]> allParams = req.getParameterMap();
1.3 Character Encoding Management
Handling Unicode characters, particularly Chinese, requires explicit encoding configuration. The default ISO-8859-1 charset in older containers often causes mojibake.
POST Payload Resolution
Set the character set before reading any parameters or streams.
req.setCharacterEncoding("UTF-8");
String input = req.getParameter("message");
GET Query String Resolution
Modern Tomcat versions (8.0+) default to UTF-8 for URL decoding. For legacy environments or custom routing, manual transcoding may be required:
String rawParam = req.getParameter("message");
// Transcode from default ISO-8859-1 back to bytes, then decode as UTF-8
String corrected = new String(rawParam.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
1.4 Server-Side Forwarding & Scope Sharing
Forwarding delegates request processing to another internal resource within the same container lifecycle. The browser remains unaware of the internal handoff.
// Transfer control to another servlet/JSP
req.getRequestDispatcher("/dashboard").forward(req, res);
Data can be shared across the forwarding chain using the request scope:
req.setAttribute("userProfile", profileObj); // Store
Object data = req.getAttribute("userProfile"); // Retrieve
req.removeAttribute("userProfile"); // Clean up
Key Characteristics: Single HTTP transaction, URL remains static, restricted to intra-server resources, request attributes persist throughout the chain.
- HttpServletResponse: Crafting Server Output
2.1 Inheritance Hierarchy
| Component | Description |
|---|---|
ServletResponse |
Base interface for protocol-independent resposne handling. |
↑ |
-- |
HttpServletResponse |
HTTP-specific extension managing status codes, headers, and redirects. |
↑ |
-- |
Container Implementation (e.g., ResponseFacade) |
Tomcat's wrapper that buffers output and commits it to the socket. |
2.2 Response Configuration & Streams
The container uses the response object to assemble the HTTP reply line, headers, and body before flushing to the network.
// Status line
res.setStatus(201);
// Metadata
res.setHeader("Cache-Control", "no-store");
res.setContentType("application/json;charset=UTF-8");
// Output channels
PrintWriter textStream = res.getWriter(); // Character-based
ServletOutputStream binaryStream = res.getOutputStream(); // Byte-based
2.3 Client-Side Redirection
Redirection instructs the browser to initiate a new HTTP request to a different location. It relies on the 302 Found status and Location header.
res.sendRedirect(req.getContextPath() + "/secure-portal");
Contrast with Forwarding: Triggers a fresh browser request, modifies the address bar, permits external URLs, and invalidates the previous request scope.
2.4 Path Resolution Strategy
Understanding relative vs. absolute paths prevents routing failures:
- Client-Facing Paths (HTML
href,form action,sendRedirect): Require the context path (virtual directory) to ensure the browser locates the resource correctly across deployments. - Server-Facing Paths (
getRequestDispatcher, JSPinclude): Omit the context path, as the container resolves them relative to the web root.
Dynamic retrieval ensures deployment flexibility:
String base = req.getContextPath(); // e.g., "/myapp"
String redirectUrl = base + "/login";
2.5 Outputting Text & Binary Data
Character streams automatically handle encoding based on the ContentType. Binary streams are ideal for files, images, or serialized objects.
Text/HTML Response
res.setContentType("text/html;charset=UTF-8");
PrintWriter out = res.getWriter();
out.println("<h1>Welcome to the portal</h1>");
Binary File Streaming
res.setContentType("image/jpeg");
try (InputStream source = new FileInputStream("/data/assets/logo.jpg");
ServletOutputStream target = res.getOutputStream()) {
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = source.read(buffer)) != -1) {
target.write(buffer, 0, bytesRead);
}
target.flush();
}
- Practical Implementation: Authentication Module
3.1 Architecture & Dependencies
This module demonstrates handling user registration and login using Servlets combined with MyBatis for persistence. The project requires standard web dependencies, database drivers, and persistence libraries.
<dependencies>
<!-- Servlet API -->
<dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>5.0.0</version><scope>provided</scope></dependency>
<!-- MyBatis & MySQL -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version></dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency>
<!-- Utility -->
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.13.0</version></dependency>
</dependencies>
3.2 Data Layer Configuration
The tb_account table stores credentials. MyBatis maps the relational data to a plain Java object.
CREATE TABLE tb_account (
id INT PRIMARY KEY AUTO_INCREMENT,
login_id VARCHAR(50) UNIQUE NOT NULL,
passcode VARCHAR(128) NOT NULL
);
Entity class:
public class AccountRecord {
private Integer id;
private String loginId;
private String passcode;
// Constructors, Getters, Setters omitted for brevity
}
Mapper interface using annotations for brevity:
@Mapper
public interface AccountRepository {
@Select("SELECT * FROM tb_account WHERE login_id = #{id} LIMIT 1")
AccountRecord findByLoginId(@Param("id") String loginId);
@Select("SELECT * FROM tb_account WHERE login_id = #{id} AND passcode = #{pass} LIMIT 1")
AccountRecord authenticate(@Param("id") String loginId, @Param("pass") String passcode);
@Insert("INSERT INTO tb_account(login_id, passcode) VALUES(#{id}, #{pass})")
int insertNewAccount(@Param("id") String loginId, @Param("pass") String passcode);
}
3.3 Centralized Session Factory
Instantiating SqlSessionFactory repeatedly is resource-intensive. A thread-safe singleton pattern ensures the heavy configuration is parsed once during class loading.
public final class PersistenceSessionFactory {
private static final SqlSessionFactory INSTANCE;
static {
try {
InputStream configStream = Resources.getResourceAsStream("mybatis-config.xml");
INSTANCE = new SqlSessionFactoryBuilder().build(configStream);
} catch (IOException e) {
throw new RuntimeException("Failed to initialize database session factory", e);
}
}
public static SqlSessionFactory getInstance() {
return INSTANCE;
}
}
3.4 Authentication Controllers
The servlets handle HTTP POST requests, resolve character encoding, interact with the repository, and dispatch appropriate responses.
Login Handler
@WebServlet("/auth/login")
public class AuthLoginController extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
// 1. Configure encoding before parameter extraction
req.setCharacterEncoding("UTF-8");
String loginId = req.getParameter("loginId");
String passcode = req.getParameter("passcode");
// 2. Database interaction
SqlSession session = PersistenceSessionFactory.getInstance().openSession(true);
try {
AccountRepository repo = session.getMapper(AccountRepository.class);
AccountRecord matched = repo.authenticate(loginId, passcode);
// 3. Craft response
res.setContentType("text/plain;charset=UTF-8");
PrintWriter out = res.getWriter();
if (matched != null) {
out.println("SUCCESS: Authenticated user " + matched.getLoginId());
} else {
res.setStatus(401);
out.println("FAILURE: Invalid credentials provided.");
}
} finally {
session.close();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
doPost(req, res);
}
}
Registration Handler
@WebServlet("/auth/register")
public class AuthRegisterController extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String newLoginId = req.getParameter("loginId");
String newPasscode = req.getParameter("passcode");
SqlSession session = PersistenceSessionFactory.getInstance().openSession(true);
try {
AccountRepository repo = session.getMapper(AccountRepository.class);
AccountRecord existing = repo.findByLoginId(newLoginId);
res.setContentType("text/plain;charset=UTF-8");
PrintWriter out = res.getWriter();
if (existing == null) {
int rowsAffected = repo.insertNewAccount(newLoginId, newPasscode);
if (rowsAffected > 0) {
out.println("REGISTERED: Account created successfully.");
} else {
res.setStatus(500);
out.println("ERROR: Database insertion failed.");
}
} else {
res.setStatus(409);
out.println("CONFLICT: Login ID already exists.");
}
} finally {
session.close();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
doPost(req, res);
}
}
3.5 Client Interface Integration
HTML forms should target the servlet mappings using relative paths or dynamic context resolution. The browser handles the HTTP POST submission automatical.
<form action="${pageContext.request.contextPath}/auth/login" method="post">
<input type="text" name="loginId" required placeholder="Username">
<input type="password" name="passcode" required placeholder="Password">
<button type="submit">Sign In</button>
</form>
By decoupling the persistence factory, standardizing character encoding practices, and clearly distinguishing between forwarding and redirection, the servlet layer becomes highly maintainable and aligned with modern Java web architecture standards.