Java Servlets and Web Application Development
Java Servlets
Java Servlets are server-side Java programs that handle client requests and generate dynamic web content. They form the foundation of Java web applications and provide a powerful, portable way to extend web server functionality.
Table of Contents
- Servlet Fundamentals
- Servlet API
- Servlet Lifecycle
- Request and Response Handling
- Session Management
- Servlet Configuration
- Filters and Listeners
- Best Practices
Servlet Fundamentals
What are Servlets?
Servlets are Java classes that extend the capabilities of servers that host applications accessed by way of a request-response programming model. They're primarily used to:
- Process client requests dynamically
- Generate dynamic web content
- Manage sessions and state
- Interact with databases and other resources
Basic Servlet Structure
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.*;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>Hello Servlet</title></head>");
out.println("<body>");
out.println("<h1>Hello, World!</h1>");
out.println("</body>");
out.println("</html>");
}
}
Servlet API
Core Interfaces and Classes
HttpServlet Class
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Handle GET requests
handleGetRequest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Handle POST requests
handlePostRequest(req, resp);
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Handle PUT requests
handlePutRequest(req, resp);
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Handle DELETE requests
handleDeleteRequest(req, resp);
}
private void handleGetRequest(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("application/json");
resp.getWriter().write("{\"message\": \"User data retrieved\"}");
}
private void handlePostRequest(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("application/json");
resp.setStatus(HttpServletResponse.SC_CREATED);
resp.getWriter().write("{\"message\": \"User created\"}");
}
}
ServletRequest and ServletResponse
public class RequestResponseExample extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Reading request parameters
String username = request.getParameter("username");
String email = request.getParameter("email");
String[] hobbies = request.getParameterValues("hobbies");
// Reading request headers
String userAgent = request.getHeader("User-Agent");
String contentType = request.getContentType();
// Reading request body (for JSON/XML)
StringBuilder requestBody = new StringBuilder();
try (BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
requestBody.append(line);
}
}
// Setting response properties
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
// Adding response headers
response.setHeader("Access-Control-Allow-Origin", "*");
response.addCookie(new Cookie("lastVisit",
String.valueOf(System.currentTimeMillis())));
// Writing response
PrintWriter out = response.getWriter();
out.println("{");
out.println(" \"status\": \"success\",");
out.println(" \"username\": \"" + username + "\",");
out.println(" \"email\": \"" + email + "\"");
out.println("}");
}
}
Servlet Lifecycle
Lifecycle Methods
public class LifecycleServlet extends HttpServlet {
private int accessCount = 0;
@Override
public void init() throws ServletException {
// Called once when servlet is first loaded
System.out.println("Servlet initialized");
// Initialize resources
initializeDatabase();
loadConfiguration();
}
@Override
public void init(ServletConfig config) throws ServletException {
// Called with servlet configuration
super.init(config);
String dbUrl = config.getInitParameter("database.url");
String dbUser = config.getInitParameter("database.user");
System.out.println("Database URL: " + dbUrl);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Called for each request
accessCount++;
System.out.println("Request #" + accessCount);
// Delegate to appropriate method (doGet, doPost, etc.)
super.service(req, resp);
}
@Override
public void destroy() {
// Called when servlet is unloaded
System.out.println("Servlet destroyed. Total requests: " + accessCount);
// Cleanup resources
closeDatabase();
saveStatistics();
}
private void initializeDatabase() {
// Database initialization logic
}
private void loadConfiguration() {
// Configuration loading logic
}
private void closeDatabase() {
// Database cleanup logic
}
private void saveStatistics() {
// Statistics saving logic
}
}
Request and Response Handling
Processing Form Data
@WebServlet("/user-registration")
public class UserRegistrationServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Set request encoding for international characters
request.setCharacterEncoding("UTF-8");
// Extract form data
String firstName = request.getParameter("firstName");
String lastName = request.getParameter("lastName");
String email = request.getParameter("email");
String password = request.getParameter("password");
String birthDate = request.getParameter("birthDate");
String[] interests = request.getParameterValues("interests");
// Validation
List<String> errors = validateUserData(firstName, lastName, email, password);
if (!errors.isEmpty()) {
// Forward to error page with validation errors
request.setAttribute("errors", errors);
request.setAttribute("formData", createFormDataMap(request));
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/error.jsp");
dispatcher.forward(request, response);
return;
}
try {
// Create user object
User user = new User();
user.setFirstName(firstName);
user.setLastName(lastName);
user.setEmail(email);
user.setPassword(hashPassword(password));
user.setBirthDate(LocalDate.parse(birthDate));
user.setInterests(Arrays.asList(interests != null ? interests : new String[0]));
// Save to database
UserService userService = new UserService();
userService.createUser(user);
// Redirect to success page
response.sendRedirect(request.getContextPath() + "/registration-success");
} catch (Exception e) {
// Handle registration error
request.setAttribute("error", "Registration failed: " + e.getMessage());
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/error.jsp");
dispatcher.forward(request, response);
}
}
private List<String> validateUserData(String firstName, String lastName,
String email, String password) {
List<String> errors = new ArrayList<>();
if (firstName == null || firstName.trim().isEmpty()) {
errors.add("First name is required");
}
if (lastName == null || lastName.trim().isEmpty()) {
errors.add("Last name is required");
}
if (email == null || !isValidEmail(email)) {
errors.add("Valid email is required");
}
if (password == null || password.length() < 8) {
errors.add("Password must be at least 8 characters");
}
return errors;
}
private boolean isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
private String hashPassword(String password) {
// Implement proper password hashing
return password; // Simplified for example
}
private Map<String, String> createFormDataMap(HttpServletRequest request) {
Map<String, String> formData = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
if (values.length > 0) {
formData.put(key, values[0]);
}
});
return formData;
}
}
File Upload Handling
@WebServlet("/file-upload")
@MultipartConfig(
fileSizeThreshold = 1024 * 1024 * 2, // 2MB
maxFileSize = 1024 * 1024 * 10, // 10MB
maxRequestSize = 1024 * 1024 * 50 // 50MB
)
public class FileUploadServlet extends HttpServlet {
private static final String UPLOAD_DIR = "uploads";
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Get the absolute path of the web application
String applicationPath = request.getServletContext().getRealPath("");
String uploadFilePath = applicationPath + File.separator + UPLOAD_DIR;
// Create the upload directory if it doesn't exist
File uploadDir = new File(uploadFilePath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
try {
// Process each uploaded file
for (Part part : request.getParts()) {
String fileName = getFileName(part);
if (fileName != null && !fileName.isEmpty()) {
// Validate file type
if (!isValidFileType(fileName)) {
response.getWriter().println("Invalid file type: " + fileName);
continue;
}
// Generate unique filename to prevent conflicts
String uniqueFileName = generateUniqueFileName(fileName);
String filePath = uploadFilePath + File.separator + uniqueFileName;
// Save the file
part.write(filePath);
// Log file upload
System.out.println("File uploaded: " + uniqueFileName);
// You could also save file metadata to database here
saveFileMetadata(uniqueFileName, part.getSize(), part.getContentType());
}
}
response.getWriter().println("Files uploaded successfully!");
} catch (Exception e) {
response.getWriter().println("Error uploading files: " + e.getMessage());
}
}
private String getFileName(Part part) {
String contentDisposition = part.getHeader("content-disposition");
String[] tokens = contentDisposition.split(";");
for (String token : tokens) {
if (token.trim().startsWith("filename")) {
return token.substring(token.indexOf("=") + 2, token.length() - 1);
}
}
return null;
}
private boolean isValidFileType(String fileName) {
String[] allowedExtensions = {".jpg", ".jpeg", ".png", ".gif", ".pdf", ".doc", ".docx"};
String lowerCaseFileName = fileName.toLowerCase();
for (String ext : allowedExtensions) {
if (lowerCaseFileName.endsWith(ext)) {
return true;
}
}
return false;
}
private String generateUniqueFileName(String originalFileName) {
String timestamp = String.valueOf(System.currentTimeMillis());
String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
return timestamp + "_" + originalFileName.replaceAll("[^a-zA-Z0-9\\.\\-]", "_");
}
private void saveFileMetadata(String fileName, long size, String contentType) {
// Save file metadata to database
// This would typically involve a DAO or service class
}
}
Session Management
Session Handling
@WebServlet("/session-demo")
public class SessionDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
// Get session information
String sessionId = session.getId();
long creationTime = session.getCreationTime();
long lastAccessTime = session.getLastAccessedTime();
int maxInactiveInterval = session.getMaxInactiveInterval();
// Session attributes
Integer visitCount = (Integer) session.getAttribute("visitCount");
if (visitCount == null) {
visitCount = 1;
} else {
visitCount++;
}
session.setAttribute("visitCount", visitCount);
// Set session timeout (in seconds)
session.setMaxInactiveInterval(30 * 60); // 30 minutes
// Response
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h2>Session Information</h2>");
out.println("<p>Session ID: " + sessionId + "</p>");
out.println("<p>Creation Time: " + new Date(creationTime) + "</p>");
out.println("<p>Last Access Time: " + new Date(lastAccessTime) + "</p>");
out.println("<p>Max Inactive Interval: " + maxInactiveInterval + " seconds</p>");
out.println("<p>Visit Count: " + visitCount + "</p>");
out.println("<a href='" + request.getRequestURI() + "'>Refresh</a>");
out.println("</body></html>");
}
}
@WebServlet("/shopping-cart")
public class ShoppingCartServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
String productId = request.getParameter("productId");
HttpSession session = request.getSession();
// Get or create shopping cart
@SuppressWarnings("unchecked")
Map<String, CartItem> cart = (Map<String, CartItem>) session.getAttribute("cart");
if (cart == null) {
cart = new HashMap<>();
session.setAttribute("cart", cart);
}
switch (action) {
case "add":
addToCart(cart, productId, request);
break;
case "remove":
removeFromCart(cart, productId);
break;
case "clear":
cart.clear();
break;
}
// Calculate totals
double total = cart.values().stream()
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
session.setAttribute("cartTotal", total);
session.setAttribute("cartItemCount", cart.size());
// Redirect back to shopping page
response.sendRedirect(request.getContextPath() + "/shop");
}
private void addToCart(Map<String, CartItem> cart, String productId,
HttpServletRequest request) {
if (cart.containsKey(productId)) {
CartItem item = cart.get(productId);
item.setQuantity(item.getQuantity() + 1);
} else {
// In real application, you'd fetch product details from database
String productName = request.getParameter("productName");
double price = Double.parseDouble(request.getParameter("price"));
CartItem item = new CartItem(productId, productName, price, 1);
cart.put(productId, item);
}
}
private void removeFromCart(Map<String, CartItem> cart, String productId) {
cart.remove(productId);
}
}
Servlet Configuration
Web.xml Configuration
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<display-name>My Web Application</display-name>
<!-- Servlet declarations -->
<servlet>
<servlet-name>UserServlet</servlet-name>
<servlet-class>com.example.UserServlet</servlet-class>
<init-param>
<param-name>database.url</param-name>
<param-value>jdbc:mysql://localhost:3306/mydb</param-value>
</init-param>
<init-param>
<param-name>database.user</param-name>
<param-value>user</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Servlet mappings -->
<servlet-mapping>
<servlet-name>UserServlet</servlet-name>
<url-pattern>/users/*</url-pattern>
</servlet-mapping>
<!-- Context parameters -->
<context-param>
<param-name>app.name</param-name>
<param-value>My Application</param-value>
</context-param>
<!-- Session configuration -->
<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<name>JSESSIONID</name>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>
<!-- Welcome files -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Error pages -->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/error/500.jsp</location>
</error-page>
</web-app>
Annotation-based Configuration
@WebServlet(
name = "UserServlet",
urlPatterns = {"/users", "/users/*"},
initParams = {
@WebInitParam(name = "database.url", value = "jdbc:mysql://localhost:3306/mydb"),
@WebInitParam(name = "database.user", value = "user")
},
loadOnStartup = 1
)
public class UserServlet extends HttpServlet {
@Override
public void init() throws ServletException {
String dbUrl = getInitParameter("database.url");
String dbUser = getInitParameter("database.user");
// Initialize database connection
}
}
Filters and Listeners
Servlet Filters
@WebFilter("/*")
public class LoggingFilter implements Filter {
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
System.out.println("LoggingFilter initialized");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Log request details
String method = httpRequest.getMethod();
String uri = httpRequest.getRequestURI();
String queryString = httpRequest.getQueryString();
String userAgent = httpRequest.getHeader("User-Agent");
long startTime = System.currentTimeMillis();
System.out.println("Request: " + method + " " + uri +
(queryString != null ? "?" + queryString : ""));
try {
// Continue the filter chain
chain.doFilter(request, response);
} finally {
// Log response details
long duration = System.currentTimeMillis() - startTime;
int status = httpResponse.getStatus();
System.out.println("Response: " + status + " (" + duration + "ms)");
}
}
@Override
public void destroy() {
System.out.println("LoggingFilter destroyed");
}
}
@WebFilter("/secure/*")
public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
boolean isLoggedIn = (session != null && session.getAttribute("user") != null);
if (!isLoggedIn) {
// User not logged in, redirect to login page
httpResponse.sendRedirect(httpRequest.getContextPath() + "/login");
return;
}
// User is logged in, continue with request
chain.doFilter(request, response);
}
}
Event Listeners
@WebListener
public class ApplicationListener implements ServletContextListener,
HttpSessionListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
System.out.println("Application starting up...");
// Initialize application-wide resources
initializeDatabase();
loadConfiguration();
// Set application attributes
context.setAttribute("appStartTime", System.currentTimeMillis());
context.setAttribute("appName", "My Web Application");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Application shutting down...");
// Cleanup application resources
closeDatabase();
saveApplicationState();
}
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("Session created: " + session.getId());
// Track active sessions
ServletContext context = session.getServletContext();
Integer activeSessions = (Integer) context.getAttribute("activeSessions");
if (activeSessions == null) {
activeSessions = 0;
}
context.setAttribute("activeSessions", activeSessions + 1);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("Session destroyed: " + session.getId());
// Update active session count
ServletContext context = session.getServletContext();
Integer activeSessions = (Integer) context.getAttribute("activeSessions");
if (activeSessions != null && activeSessions > 0) {
context.setAttribute("activeSessions", activeSessions - 1);
}
}
private void initializeDatabase() {
// Database initialization logic
}
private void loadConfiguration() {
// Configuration loading logic
}
private void closeDatabase() {
// Database cleanup logic
}
private void saveApplicationState() {
// Save application state logic
}
}
Best Practices
1. Thread Safety
public class ThreadSafeServlet extends HttpServlet {
// Class-level variables are shared among all requests - avoid mutable state
private final UserService userService = new UserService(); // OK - immutable reference
private int counter = 0; // BAD - mutable shared state
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Local variables are thread-safe
String userId = request.getParameter("userId");
User user = userService.findById(userId);
// Synchronized access to shared mutable state
synchronized (this) {
counter++;
System.out.println("Request count: " + counter);
}
// Better approach: use application context for shared data
ServletContext context = getServletContext();
synchronized (context) {
Integer appCounter = (Integer) context.getAttribute("requestCount");
if (appCounter == null) appCounter = 0;
context.setAttribute("requestCount", appCounter + 1);
}
}
}
2. Resource Management
public class ResourceManagementServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// Get database connection
connection = DatabaseUtil.getConnection();
// Prepare statement
statement = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
statement.setString(1, request.getParameter("userId"));
// Execute query
resultSet = statement.executeQuery();
// Process results
if (resultSet.next()) {
// Build response
response.setContentType("application/json");
response.getWriter().write(buildUserJson(resultSet));
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
} catch (SQLException e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("{\"error\": \"Database error\"}");
} finally {
// Always close resources in reverse order
closeQuietly(resultSet);
closeQuietly(statement);
closeQuietly(connection);
}
}
private void closeQuietly(AutoCloseable resource) {
if (resource != null) {
try {
resource.close();
} catch (Exception e) {
// Log error but don't throw
System.err.println("Error closing resource: " + e.getMessage());
}
}
}
private String buildUserJson(ResultSet rs) throws SQLException {
return String.format(
"{\"id\": \"%s\", \"name\": \"%s\", \"email\": \"%s\"}",
rs.getString("id"),
rs.getString("name"),
rs.getString("email")
);
}
}
3. Error Handling
public class ErrorHandlingServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try {
// Validate input
validateRequest(request);
// Process request
processUserRegistration(request, response);
} catch (ValidationException e) {
sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
"Validation Error", e.getMessage());
} catch (DuplicateUserException e) {
sendErrorResponse(response, HttpServletResponse.SC_CONFLICT,
"Duplicate User", e.getMessage());
} catch (Exception e) {
// Log unexpected errors
System.err.println("Unexpected error in user registration: " + e.getMessage());
e.printStackTrace();
sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Internal Error", "An unexpected error occurred");
}
}
private void validateRequest(HttpServletRequest request) throws ValidationException {
String email = request.getParameter("email");
String password = request.getParameter("password");
if (email == null || email.trim().isEmpty()) {
throw new ValidationException("Email is required");
}
if (!isValidEmail(email)) {
throw new ValidationException("Invalid email format");
}
if (password == null || password.length() < 8) {
throw new ValidationException("Password must be at least 8 characters");
}
}
private void sendErrorResponse(HttpServletResponse response, int statusCode,
String error, String message) throws IOException {
response.setStatus(statusCode);
response.setContentType("application/json");
response.getWriter().write(String.format(
"{\"error\": \"%s\", \"message\": \"%s\"}", error, message));
}
}
Summary
Java Servlets provide a powerful foundation for building web applications with several key advantages:
Core Benefits:
- Platform independence and portability
- Robust lifecycle management
- Built-in session and state management
- Integration with Java ecosystem
Key Concepts:
- Servlet lifecycle (init, service, destroy)
- Request/response handling patterns
- Session management techniques
- Filter and listener integration
Best Practices:
- Ensure thread safety in servlet implementations
- Proper resource management and cleanup
- Comprehensive error handling and validation
- Use filters for cross-cutting concerns
- Leverage annotations for cleaner configuration
Modern Usage: While modern frameworks like Spring Boot have largely abstracted away direct servlet programming, understanding servlets remains important for:
- Debugging web application issues
- Understanding framework internals
- Implementing custom filters and listeners
- Performance optimization
Servlets continue to be the foundation that powers Java web applications, providing the essential request-response processing capabilities that higher-level frameworks build upon.