Introduction
Java Servlet-based applications have been widely used for building dynamic, database-driven web applications. In this tutorial, we will explore how to create a CRUD (Create, Read, Update, Delete) application using Java Servlets with image upload functionality. This application will integrate several key components: Jakarta EE 9+, Apache Tomcat 10+, and MySQL Database. We will also be utilizing JSTL (Jakarta Standard Tag Library) to simplify the JSP pages and create a more maintainable and cleaner codebase.
In this example, we will build a blog post management system where users can add, list, edit, and delete blog posts along with an image upload feature. The system will allow users to upload an image while creating or editing a post, which will be saved in the server’s directory. The application is structured to use the Servlet API for server-side processing and JSP pages to display the user interface, following the best practices for Jakarta EE.
This guide will walk you through the necessary steps, from configuring the dependencies to setting up the application logic. Whether you’re new to Java web applications or looking to enhance your existing knowledge, this tutorial will help you understand the process of building a full-fledged CRUD application with image support.
Prerequisites
- Jakarta EE 9+ (or Jakarta EE 10)
- Apache Tomcat 10+ (supports Jakarta EE)
- MySQL Database
- Eclipse IDE (or any IDE supporting Jakarta EE)
- MySQL Connector/J (JDBC Driver)
- Jakarta Standard Tag Library (JSTL) (included in Jakarta EE)
Project Struture:
BlogApp/ │── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ ├── blogapp/ │ │ │ │ │ ├── dao/ │ │ │ │ │ │ ├── PostDAO.java │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── Post.java │ │ │ │ │ ├── servlet/ │ │ │ │ │ │ ├── AddPostServlet.java │ │ │ │ │ │ ├── EditPostServlet.java │ │ │ │ │ │ ├── DeletePostServlet.java │ │ │ │ │ │ ├── ListPostsServlet.java │ │ │ │ │ ├── util/ │ │ │ │ │ │ ├── DBConnection.java │ │ ├── webapp/ │ │ │ ├── META-INF/ │ │ │ ├── WEB-INF/ │ │ │ │ ├── web.xml │ │ │ │ ├── lib/ (for manual JAR dependencies if not using Maven) │ │ │ ├── uploads/ (for storing uploaded images) │ │ │ ├── css/ │ │ │ │ ├── styles.css │ │ │ ├── jsp/ │ │ │ │ ├── listPosts.jsp │ │ │ │ ├── addPost.jsp │ │ │ │ ├── editPost.jsp │ │ │ ├── index.jsp (optional, main page) │── pom.xml (if using Maven) │── README.md
Step 1: Update Dependencies
- Add Jakarta EE Dependencies:
- If you’re using Maven, add the following dependencies to your
pom.xml
:
- If you’re using Maven, add the following dependencies to your
<dependencies> <!-- Jakarta Servlet API --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency> <!-- Jakarta JSTL --> <dependency> <groupId>jakarta.servlet.jsp.jstl</groupId> <artifactId>jakarta.servlet.jsp.jstl-api</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>jakarta.servlet.jsp.jstl</artifactId> <version>3.0.0</version> </dependency> <!-- MySQL Connector/J --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies>
- Add JARs Manually (if not using Maven):
- Download the following JARs and add them to
WEB-INF/lib
:jakarta.servlet-api.jar
jakarta.servlet.jsp.jstl-api.jar
jakarta.servlet.jsp.jstl.jar
mysql-connector-java.jar
- Download the following JARs and add them to
Data Base connection :
DBConnection.java
package com.blogapp.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBConnection { private static final String URL = "jdbc:mysql://localhost:3306/blogdb"; // Change `blogdb` to your database name private static final String USER = "root"; // Change `root` to your MySQL username private static final String PASSWORD = ""; // Change `""` to your MySQL password if any private static Connection connection = null; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); // Load MySQL Driver connection = DriverManager.getConnection(URL, USER, PASSWORD); System.out.println("Database Connected Successfully!"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); throw new RuntimeException("Database connection failed!"); } } public static Connection getConnection() { return connection; } }
Database and Table
Database: blogapp
You can create the database using:
1. posts
Table
This table stores blog posts, including images.
Fields Explanation:
id
→ Unique ID for each blog post.title
→ Title of the blog post.content
→ Content/body of the post.image
→ Stores the image filename (you can save images in a folder likeuploads/
).created_at
→ Stores the timestamp when the post is created.
Step 2: Create a PostDAO.java file
package com.blogapp.dao; import com.blogapp.model.Post; import com.blogapp.util.DBConnection; import java.sql.*; import java.util.ArrayList; import java.util.List; public class PostDAO { private Connection connection; public PostDAO(Connection connection) { this.connection = connection; } // Create a new post public boolean addPost(Post post) { String sql = "INSERT INTO posts (title, content, image, created_at) VALUES (?, ?, ?, ?)"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setString(1, post.getTitle()); statement.setString(2, post.getContent()); statement.setString(3, post.getImage()); statement.setTimestamp(4, new Timestamp(System.currentTimeMillis())); int rowsAffected = statement.executeUpdate(); return rowsAffected > 0; } catch (SQLException e) { e.printStackTrace(); } return false; } // Get all posts public List<Post> getAllPosts() { List<Post> posts = new ArrayList<>(); String sql = "SELECT * FROM posts ORDER BY created_at DESC"; try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { while (resultSet.next()) { Post post = new Post(); post.setId(resultSet.getInt("id")); post.setTitle(resultSet.getString("title")); post.setContent(resultSet.getString("content")); post.setImage(resultSet.getString("image")); post.setCreatedAt(resultSet.getTimestamp("created_at")); posts.add(post); } } catch (SQLException e) { e.printStackTrace(); } return posts; } // Get a post by its ID public Post getPostById(int id) { Post post = null; String sql = "SELECT * FROM posts WHERE id = ?"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setInt(1, id); try (ResultSet resultSet = statement.executeQuery()) { if (resultSet.next()) { post = new Post(); post.setId(resultSet.getInt("id")); post.setTitle(resultSet.getString("title")); post.setContent(resultSet.getString("content")); post.setImage(resultSet.getString("image")); post.setCreatedAt(resultSet.getTimestamp("created_at")); } } } catch (SQLException e) { e.printStackTrace(); } return post; } // Update an existing post public boolean updatePost(Post post) { String sql = "UPDATE posts SET title = ?, content = ?, image = ? WHERE id = ?"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setString(1, post.getTitle()); statement.setString(2, post.getContent()); statement.setString(3, post.getImage()); statement.setInt(4, post.getId()); int rowsAffected = statement.executeUpdate(); return rowsAffected > 0; } catch (SQLException e) { e.printStackTrace(); } return false; } // Delete a post by its ID public boolean deletePost(int id) { String sql = "DELETE FROM posts WHERE id = ?"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setInt(1, id); int rowsAffected = statement.executeUpdate(); return rowsAffected > 0; } catch (SQLException e) { e.printStackTrace(); } return false; } }
2.1 Write Modal Class
package com.blogapp.model; import java.sql.Timestamp; public class Post { private int id; private String title; private String content; private String image; private Timestamp createdAt; // Default constructor public Post() {} // Constructor with all fields public Post(int id, String title, String content, String image, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.image = image; this.createdAt = createdAt; } // Getter and Setter for id public int getId() { return id; } public void setId(int id) { this.id = id; } // Getter and Setter for title public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } // Getter and Setter for content public String getContent() { return content; } public void setContent(String content) { this.content = content; } // Getter and Setter for image public String getImage() { return image; } public void setImage(String image) { this.image = image; } // Getter and Setter for createdAt public Timestamp getCreatedAt() { return createdAt; } public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; } // Optional: toString method for easy printing @Override public String toString() { return "Post{" + "id=" + id + ", title='" + title + '\'' + ", content='" + content + '\'' + ", image='" + image + '\'' + ", createdAt=" + createdAt + '}'; } }
Step 3: Update JSP Files with JSTL
Use JSTL tags in your JSP files for cleaner and more maintainable code.
listPosts.jsp
with JSTL
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>List Posts</title> </head> <body> <h1>Blog Posts</h1> <table border="1"> <tr> <th>ID</th> <th>Title</th> <th>Content</th> <th>Image</th> <th>Created At</th> <th>Actions</th> </tr> <c:forEach var="post" items="${posts}"> <tr> <td>${post.id}</td> <td>${post.title}</td> <td>${post.content}</td> <td> <c:if test="${not empty post.image}"> <img src="uploads/${post.image}" width="100"> </c:if> </td> <td>${post.createdAt}</td> <td> <a href="editPost?id=${post.id}">Edit</a> <a href="deletePost?id=${post.id}" onclick="return confirm('Are you sure?')">Delete</a> </td> </tr> </c:forEach> </table> <br> <a href="addPost.jsp">Add New Post</a> </body> </html>
addPost.jsp
with JSTL
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Add Post</title> </head> <body> <h1>Add New Post</h1> <form action="addPost" method="post" enctype="multipart/form-data"> <label for="title">Title:</label> <input type="text" id="title" name="title" required><br><br> <label for="content">Content:</label> <textarea id="content" name="content" required></textarea><br><br> <label for="image">Image:</label> <input type="file" id="image" name="image" accept="image/*"><br><br> <input type="submit" value="Add Post"> </form> </body> </html>
editPost.jsp
with JSTL
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Edit Post</title> </head> <body> <h1>Edit Post</h1> <form action="editPost" method="post" enctype="multipart/form-data"> <input type="hidden" name="id" value="${post.id}"> <label for="title">Title:</label> <input type="text" id="title" name="title" value="${post.title}" required><br><br> <label for="content">Content:</label> <textarea id="content" name="content" required>${post.content}</textarea><br><br> <label for="image">Image:</label> <input type="file" id="image" name="image" accept="image/*"><br><br> <c:if test="${not empty post.image}"> <p>Current Image: <img src="uploads/${post.image}" width="100"></p> </c:if> <input type="submit" value="Update Post"> </form> </body> </html>
Step 4: Update Servlets for Jakarta EE
Replace javax.servlet
with jakarta.servlet
in all servlets. For example:
AddPostServlet.java
(Updated for Jakarta EE)
package com.blogapp.servlet; import com.blogapp.dao.PostDAO; import com.blogapp.model.Post; import com.blogapp.util.DBConnection; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.MultipartConfig; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.Part; import java.io.File; import java.io.IOException; import java.sql.Connection; @WebServlet("/addPost") @MultipartConfig(fileSizeThreshold = 1024 * 1024 * 2, // 2MB maxFileSize = 1024 * 1024 * 10, // 10MB maxRequestSize = 1024 * 1024 * 50) // 50MB public class AddPostServlet extends HttpServlet { private static final String UPLOAD_DIR = "uploads"; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String title = request.getParameter("title"); String content = request.getParameter("content"); // Get the absolute path of the 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(); } String fileName = ""; for (Part part : request.getParts()) { fileName = getFileName(part); if (fileName != null && !fileName.isEmpty()) { part.write(uploadFilePath + File.separator + fileName); } } Post post = new Post(); post.setTitle(title); post.setContent(content); post.setImage(fileName); Connection connection = DBConnection.getConnection(); PostDAO postDAO = new PostDAO(connection); if (postDAO.addPost(post)) { response.sendRedirect("listPosts"); } else { response.sendRedirect("addPost.jsp"); } } private String getFileName(Part part) { String contentDisp = part.getHeader("content-disposition"); String[] tokens = contentDisp.split(";"); for (String token : tokens) { if (token.trim().startsWith("filename")) { return token.substring(token.indexOf("=") + 2, token.length() - 1); } } return null; } }
ListPostServlet.java
package web; import java.io.IOException; import java.sql.Connection; import java.util.List; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import model.Post; import Dao.PostDAO; import util.DBConnection; @WebServlet("/ListPostServlet") public class ListPostServlet extends HttpServlet { private static final long serialVersionUID = 1L; public ListPostServlet() { super(); } @SuppressWarnings("resource") protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection connection = DBConnection.getConnection(); if (connection == null) { response.getWriter().append("Database connection failed."); return; } PostDAO postDAO = new PostDAO(connection); // Handle delete operation String mode = request.getParameter("mode"); String postIdParam = request.getParameter("id"); if ("del".equals(mode) && postIdParam != null) { try { int postId = Integer.parseInt(postIdParam); postDAO.deletePost(postId); } catch (NumberFormatException e) { response.getWriter().append("Invalid post ID."); return; } } // Fetch and display posts List<Post> posts = postDAO.getAllPosts(); request.setAttribute("posts", posts); request.getRequestDispatcher("listPosts.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
EditPostServlet.java
package web; import model.Post; import Dao.PostDAO; import util.DBConnection; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.MultipartConfig; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.Part; import java.io.File; import java.io.IOException; import java.sql.Connection; @WebServlet("/editPost") @MultipartConfig(fileSizeThreshold = 1024 * 1024 * 2, // 2MB maxFileSize = 1024 * 1024 * 10, // 10MB maxRequestSize = 1024 * 1024 * 50) // 50MB public class EditPostServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String UPLOAD_DIR = "uploads"; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int id = Integer.parseInt(request.getParameter("id")); Connection connection = DBConnection.getConnection(); PostDAO postDAO = new PostDAO(connection); Post post = postDAO.getPostById(id); if (post != null) { request.setAttribute("post", post); request.getRequestDispatcher("editPost.jsp").forward(request, response); } else { response.sendRedirect("listPosts"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int id = Integer.parseInt(request.getParameter("id")); String title = request.getParameter("title"); String content = request.getParameter("content"); // File upload handling String applicationPath = request.getServletContext().getRealPath(""); String uploadFilePath = applicationPath + File.separator + UPLOAD_DIR; File uploadDir = new File(uploadFilePath); if (!uploadDir.exists()) { uploadDir.mkdirs(); } String fileName = ""; for (Part part : request.getParts()) { fileName = getFileName(part); if (fileName != null && !fileName.isEmpty()) { part.write(uploadFilePath + File.separator + fileName); } } Connection connection = DBConnection.getConnection(); PostDAO postDAO = new PostDAO(connection); Post existingPost = postDAO.getPostById(id); if (existingPost != null) { existingPost.setTitle(title); existingPost.setContent(content); if (!fileName.isEmpty()) { existingPost.setImage(fileName); // Update image if a new one is uploaded } if (postDAO.updatePost(existingPost)) { response.sendRedirect("ListPostServlet"); } else { response.sendRedirect("editPost.jsp?id=" + id); } } else { response.sendRedirect("listPosts"); } } private String getFileName(Part part) { String contentDisp = part.getHeader("content-disposition"); for (String token : contentDisp.split(";")) { if (token.trim().startsWith("filename")) { return token.substring(token.indexOf("=") + 2, token.length() - 1); } } return null; } }
Step 5: Deploy and Test
- Deploy the application to Apache Tomcat 10+.
- Access the application via
http://localhost:8080/BlogApp/listPosts
.
Download code from gitHub
https://github.com/acesoftech/javaservlet-crud-with-image-upload/upload