Struts2 CRUD – Insert, Update, Delete, Retrieve with MySQL and Bootstrap
This tutorial builds a complete Struts2 CRUD application for a User management screen. The stack is: Struts2 2.5 (Action classes + struts.xml) + JDBC (raw SQL, no ORM) + MySQL + Bootstrap 5 + JSTL for the data table. All four operations — add, edit, delete, list — run on a single JSP with Bootstrap modals for the forms.
MySQL Table
CREATE TABLE user_details (
user_id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(200) NOT NULL,
mobile VARCHAR(15)
);
Maven Dependencies (pom.xml)
<dependencies>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.5.33</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
Project Structure
src/main/
java/com/java9r/
model/ UserBean.java
dao/ UserDAO.java
db/ DBConnection.java
action/ UserAction.java
resources/
struts.xml
webapp/
WEB-INF/
web.xml
index.jsp (redirects to list)
UserList.jsp (main CRUD page)
DBConnection.java
package com.java9r.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBConnection {
private static final String URL = "jdbc:mysql://localhost:3306/java9rdb?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "yourpassword";
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException("MySQL driver not found", e);
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
}
UserBean.java
package com.java9r.model;
public class UserBean {
private int userId;
private String firstName;
private String lastName;
private String email;
private String mobile;
// Getters and Setters
public int getUserId() { return userId; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String getEmail() { return email; }
public String getMobile() { return mobile; }
public void setUserId(int userId) { this.userId = userId; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public void setEmail(String email) { this.email = email; }
public void setMobile(String mobile) { this.mobile = mobile; }
}
UserDAO.java
package com.java9r.dao;
import com.java9r.db.DBConnection;
import com.java9r.model.UserBean;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserDAO {
public List<UserBean> getAllUsers() {
List<UserBean> list = new ArrayList<>();
String sql = "SELECT user_id, first_name, last_name, email, mobile FROM user_details ORDER BY user_id";
try (Connection con = DBConnection.getConnection();
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
UserBean u = new UserBean();
u.setUserId(rs.getInt("user_id"));
u.setFirstName(rs.getString("first_name"));
u.setLastName(rs.getString("last_name"));
u.setEmail(rs.getString("email"));
u.setMobile(rs.getString("mobile"));
list.add(u);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public void addUser(UserBean u) {
String sql = "INSERT INTO user_details (first_name, last_name, email, mobile) VALUES (?,?,?,?)";
try (Connection con = DBConnection.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
ps.setString(1, u.getFirstName());
ps.setString(2, u.getLastName());
ps.setString(3, u.getEmail());
ps.setString(4, u.getMobile());
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void updateUser(UserBean u) {
String sql = "UPDATE user_details SET first_name=?, last_name=?, email=?, mobile=? WHERE user_id=?";
try (Connection con = DBConnection.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
ps.setString(1, u.getFirstName());
ps.setString(2, u.getLastName());
ps.setString(3, u.getEmail());
ps.setString(4, u.getMobile());
ps.setInt(5, u.getUserId());
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void deleteUser(int userId) {
String sql = "DELETE FROM user_details WHERE user_id=?";
try (Connection con = DBConnection.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
ps.setInt(1, userId);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
UserAction.java
The Action class holds the form bean as a property. Struts2 binds nested properties like user.firstName from HTTP parameters automatically via OGNL.
package com.java9r.action;
import com.java9r.dao.UserDAO;
import com.java9r.model.UserBean;
import com.opensymphony.xwork2.ActionSupport;
import java.util.List;
public class UserAction extends ActionSupport {
private static final long serialVersionUID = 1L;
// Nested bean — form parameters use prefix "user." e.g. user.firstName
private UserBean user = new UserBean();
private List<UserBean> users;
private final UserDAO dao = new UserDAO();
// List all users — mapped to /listUsers
public String listUsers() {
users = dao.getAllUsers();
return SUCCESS;
}
// Add new user — mapped to /addUser
public String addUser() {
dao.addUser(user);
users = dao.getAllUsers();
user = new UserBean();
return SUCCESS;
}
// Update user — mapped to /updateUser
public String updateUser() {
dao.updateUser(user);
users = dao.getAllUsers();
user = new UserBean();
return SUCCESS;
}
// Delete user — mapped to /deleteUser
public String deleteUser() {
dao.deleteUser(user.getUserId());
users = dao.getAllUsers();
user = new UserBean();
return SUCCESS;
}
// Getters and Setters
public UserBean getUser() { return user; }
public List<UserBean> getUsers() { return users; }
public void setUser(UserBean user) { this.user = user; }
public void setUsers(List<UserBean> users) { this.users = users; }
}
struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.devMode" value="false"/>
<constant name="struts.enable.DynamicMethodInvocation" value="false"/>
<package name="user" namespace="/" extends="struts-default">
<action name="listUsers"
class="com.java9r.action.UserAction"
method="listUsers">
<result name="success">/UserList.jsp</result>
</action>
<action name="addUser"
class="com.java9r.action.UserAction"
method="addUser">
<result name="success" type="redirectAction">listUsers</result>
</action>
<action name="updateUser"
class="com.java9r.action.UserAction"
method="updateUser">
<result name="success" type="redirectAction">listUsers</result>
</action>
<action name="deleteUser"
class="com.java9r.action.UserAction"
method="deleteUser">
<result name="success" type="redirectAction">listUsers</result>
</action>
</package>
</struts>
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_4_0.xsd"
version="4.0">
<display-name>Struts2 CRUD</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
index.jsp – Redirect to List
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0; url=listUsers">
</head>
<body></body>
</html>
UserList.jsp – CRUD Table with Bootstrap Modals
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>User Management</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3>User Management</h3>
<!-- Button to open Add User modal -->
<button class="btn btn-success"
data-bs-toggle="modal" data-bs-target="#addModal">+ Add User</button>
</div>
<!-- Users Table -->
<table class="table table-bordered table-hover">
<thead class="table-dark">
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Mobile</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<c:forEach var="u" items="${users}">
<tr>
<td>${u.userId}</td>
<td>${u.firstName}</td>
<td>${u.lastName}</td>
<td>${u.email}</td>
<td>${u.mobile}</td>
<td>
<!-- Edit button opens a per-row modal -->
<button class="btn btn-sm btn-primary"
data-bs-toggle="modal"
data-bs-target="#editModal${u.userId}">Edit</button>
<!-- Delete (inline form, no modal needed) -->
<form action="deleteUser" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?')">
<input type="hidden" name="user.userId" value="${u.userId}">
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
</td>
</tr>
<!-- Edit Modal (one per row, identified by userId) -->
<div class="modal fade" id="editModal${u.userId}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form action="updateUser" method="post">
<div class="modal-header">
<h5 class="modal-title">Edit User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="user.userId" value="${u.userId}">
<div class="mb-2">
<label>First Name</label>
<input type="text" name="user.firstName"
class="form-control" value="${u.firstName}" required>
</div>
<div class="mb-2">
<label>Last Name</label>
<input type="text" name="user.lastName"
class="form-control" value="${u.lastName}" required>
</div>
<div class="mb-2">
<label>Email</label>
<input type="email" name="user.email"
class="form-control" value="${u.email}" required>
</div>
<div class="mb-2">
<label>Mobile</label>
<input type="text" name="user.mobile"
class="form-control" value="${u.mobile}">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Update</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<!-- End Edit Modal -->
</c:forEach>
</tbody>
</table>
</div>
<!-- Add User Modal -->
<div class="modal fade" id="addModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form action="addUser" method="post">
<div class="modal-header">
<h5 class="modal-title">Add New User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<label>First Name</label>
<input type="text" name="user.firstName" class="form-control" required>
</div>
<div class="mb-2">
<label>Last Name</label>
<input type="text" name="user.lastName" class="form-control" required>
</div>
<div class="mb-2">
<label>Email</label>
<input type="email" name="user.email" class="form-control" required>
</div>
<div class="mb-2">
<label>Mobile</label>
<input type="text" name="user.mobile" class="form-control">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">Save</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<!-- End Add Modal -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
How Struts2 Binds Nested Form Parameters
The form fields use the prefix user. (e.g. name="user.firstName"). Struts2 reads this as: get the user property from the action (getUser()), then call setFirstName() on that object. This is OGNL object graph navigation — it works to any depth: user.address.city, etc.
| HTML input name | Struts2 calls |
|---|---|
user.firstName |
action.getUser().setFirstName(value) |
user.userId |
action.getUser().setUserId(value) |
user.email |
action.getUser().setEmail(value) |
Summary
This Struts2 CRUD application follows the pattern: one Action class with four methods (list, add, update, delete), each mapped to its own URL in struts.xml, all returning a redirect to the list after write operations (PRG — Post/Redirect/Get pattern). The DAO layer uses try-with-resources for automatic connection cleanup. The JSP uses JSTL <c:forEach> to render the table and Bootstrap 5 modals for the Add and Edit forms. Nested bean binding (user.firstName) keeps the action's form fields organized as a single object instead of loose fields.
Comments