Mini Todo Management Project using Spring Boot + Spring MVC + Spring Security + JSP + Hibernate + MySQL
📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
- Create Todo
- Update Todo
- Delete Todo
- List Todo
- Get Todo by id.
- Simple Spring security
- Webjars to manage client-side dependencies(CSS and JS).
- JSP as view and common header, footer, and navigation bar.
- Custom error page mapping
- Finish Todo feature: Add finish button and save status against each record in a database.
- logging:
Refer below article to implement logging effectively in this Todo management project Spring Boot 2 Logging SLF4j Logback and LOG4j2 Example
- Role-based spring security:
Refer below article to implement role-based Spring security effectively in this Todo management project Spring Boot + Spring MVC + Role Based Spring Security + JPA + Thymeleaf + MySQL Tutorial
- Exception handling:
Refer below article to implement exception handling effectively in this Todo management project Spring Boot 2 Exception Handling for REST APIs
- Validation:
Refer below article to implement validation effectively in this Todo management project Spring Boot CRUD REST APIs Validation Example
- Auditing:
Refer below article to implement auditing effectively in this Todo management project Spring Data JPA Auditing with Spring Boot 2 and MySQL Example
Video Tutorial
What we’ll build
Tools and Technologies Used
- Spring Boot - 2.0.4.RELEASE
- JDK - 1.8 or later
- Spring Framework - 5.0.8 RELEASE
- Hibernate - 5.2.17.Final
- Maven - 3.2+
- Spring Data JPA - 2.0.10 RELEASE
- IDE - Eclipse or Spring Tool Suite (STS)
- MYSQL - 5.1.47
- Spring Security - 5.0.7 RELEASE
- JSP
Creating and Importing a Project
- Generate: Maven Project
- Java Version: 1.8 (Default)
- Spring Boot:2.0.4
- Group: net.guides.todo
- Artifact: todo-management-spring-boot
- Name: todo-management-spring-boot
- Description: todo-management-spring-boot
- Package Name : net.guides.springboot.todomanagement
- Packaging: jar (This is the default value)
- Dependencies: Web, JPA, MySQL, DevTools, Security
Packaging Structure
The pom.xml File
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.guides.springboot</groupId> <artifactId>todo-management-spring-boot</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>todo-management-spring-boot</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.6</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap-datepicker</artifactId> <version>1.0.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>1.9.1</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Note that we have used Spring Data JPA starter to talk to MySQL database.
Create the JPA Entity - Todo.java
package net.guides.springboot.todomanagement.model;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Size;
/**
* @author Ramesh Fadatare
*
*/
@Entity
@Table(name = "todos")
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String userName;
@Size(min = 10, message = "Enter at least 10 Characters...")
private String description;
private Date targetDate;
public Todo() {
super();
}
public Todo(String user, String desc, Date targetDate, boolean isDone) {
super();
this.userName = user;
this.description = desc;
this.targetDate = targetDate;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getTargetDate() {
return targetDate;
}
public void setTargetDate(Date targetDate) {
this.targetDate = targetDate;
}
}
Spring Data JPA Repository Interface - TodoRepository.java
package net.guides.springboot.todomanagement.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import net.guides.springboot.todomanagement.model.Todo;
public interface TodoRepository extends JpaRepository < Todo, Long > {
List < Todo > findByUserName(String user);
}
Spring Security Configuration - SecurityConfiguration.java
package net.guides.springboot.todomanagement.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("admin").password("admin")
.roles("USER", "ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login", "/h2-console/**").permitAll()
.antMatchers("/", "/*todo*/**").access("hasRole('USER')").and()
.formLogin();
http.csrf().disable();
http.headers().frameOptions().disable();
}
}
Controller Layer - TodoController.java
package net.guides.springboot.todomanagement.controller;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import net.guides.springboot.todomanagement.model.Todo;
import net.guides.springboot.todomanagement.service.ITodoService;
@Controller
public class TodoController {
@Autowired
private ITodoService todoService;
@InitBinder
public void initBinder(WebDataBinder binder) {
// Date - dd/MM/yyyy
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@RequestMapping(value = "/list-todos", method = RequestMethod.GET)
public String showTodos(ModelMap model) {
String name = getLoggedInUserName(model);
model.put("todos", todoService.getTodosByUser(name));
// model.put("todos", service.retrieveTodos(name));
return "list-todos";
}
private String getLoggedInUserName(ModelMap model) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
}
return principal.toString();
}
@RequestMapping(value = "/add-todo", method = RequestMethod.GET)
public String showAddTodoPage(ModelMap model) {
model.addAttribute("todo", new Todo());
return "todo";
}
@RequestMapping(value = "/delete-todo", method = RequestMethod.GET)
public String deleteTodo(@RequestParam long id) {
todoService.deleteTodo(id);
// service.deleteTodo(id);
return "redirect:/list-todos";
}
@RequestMapping(value = "/update-todo", method = RequestMethod.GET)
public String showUpdateTodoPage(@RequestParam long id, ModelMap model) {
Todo todo = todoService.getTodoById(id).get();
model.put("todo", todo);
return "todo";
}
@RequestMapping(value = "/update-todo", method = RequestMethod.POST)
public String updateTodo(ModelMap model, @Valid Todo todo, BindingResult result) {
if (result.hasErrors()) {
return "todo";
}
todo.setUserName(getLoggedInUserName(model));
todoService.updateTodo(todo);
return "redirect:/list-todos";
}
@RequestMapping(value = "/add-todo", method = RequestMethod.POST)
public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) {
if (result.hasErrors()) {
return "todo";
}
todo.setUserName(getLoggedInUserName(model));
todoService.saveTodo(todo);
return "redirect:/list-todos";
}
}
Controller Layer - WelcomeController.java
package net.guides.springboot.todomanagement.controller;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class WelcomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String showWelcomePage(ModelMap model) {
model.put("name", getLoggedinUserName());
return "welcome";
}
private String getLoggedinUserName() {
Object principal = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
}
return principal.toString();
}
}
Controller Layer - ErrorController.java
package net.guides.springboot.todomanagement.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@Controller("error")
public class ErrorController {
@ExceptionHandler(Exception.class)
public ModelAndView handleException(HttpServletRequest request, Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("exception", ex.getLocalizedMessage());
mv.addObject("url", request.getRequestURL());
mv.setViewName("error");
return mv;
}
}
Controller Layer - LogoutController.java
package net.guides.springboot.todomanagement.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class LogoutController {
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpServletRequest request,
HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication != null) {
new SecurityContextLogoutHandler().logout(request, response,
authentication);
}
return "redirect:/";
}
}
Service Layer - ITodoService.java
package net.guides.springboot.todomanagement.service;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import net.guides.springboot.todomanagement.model.Todo;
public interface ITodoService {
List < Todo > getTodosByUser(String user);
Optional < Todo > getTodoById(long id);
void updateTodo(Todo todo);
void addTodo(String name, String desc, Date targetDate, boolean isDone);
void deleteTodo(long id);
void saveTodo(Todo todo);
}
Service Layer - TodoService.java
package net.guides.springboot.todomanagement.service;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import net.guides.springboot.todomanagement.model.Todo;
import net.guides.springboot.todomanagement.repository.TodoRepository;
@Service
public class TodoService implements ITodoService {
@Autowired
private TodoRepository todoRepository;
@Override
public List < Todo > getTodosByUser(String user) {
return todoRepository.findByUserName(user);
}
@Override
public Optional < Todo > getTodoById(long id) {
return todoRepository.findById(id);
}
@Override
public void updateTodo(Todo todo) {
todoRepository.save(todo);
}
@Override
public void addTodo(String name, String desc, Date targetDate, boolean isDone) {
todoRepository.save(new Todo(name, desc, targetDate, isDone));
}
@Override
public void deleteTodo(long id) {
Optional < Todo > todo = todoRepository.findById(id);
if (todo.isPresent()) {
todoRepository.delete(todo.get());
}
}
@Override
public void saveTodo(Todo todo) {
todoRepository.save(todo);
}
}
Configuring MySQL Database and JSP View Resolver
## Spring view resolver set up
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/users_database?useSSL=false
spring.datasource.username = root
spring.datasource.password = root
## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
Common JSP page fragments - footer.jspf, header.jspf and navigation.jspf
footer.jspf View
<script src="webjars/jquery/1.9.1/jquery.min.js"></script> <script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script> <script src="webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js"></script> <script> $('#targetDate').datepicker({ format: 'dd/mm/yyyy' }); </script> <div class="footer"> Fixed Footer <h1> <a href="http://www.javaguides.net/p/spring-boot-tutorial.html"> Spring Boot Tutorial</a> </h1> </div> </body> </html>
header.jspf
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
<title>Todo Management</title>
<link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css"
rel="stylesheet">
<style>
.footer {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
background-color: black;
color: white;
height: 100px;
text-align: center;
}
</style>
</head>
<body>
navigation.jspf View
<nav role="navigation" class="navbar navbar-default">
<div class="">
<a href="http://www.javaguides.net" class="navbar-brand">Java Guides</a>
</div>
<div class="navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="/">Home</a></li>
<li><a href="/list-todos">Todos</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/logout">Logout</a></li>
</ul>
</div>
</nav>
Welcome Page View
<%@ include file="common/header.jspf"%>
<%@ include file="common/navigation.jspf"%>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">Home Page</div>
<div class="panel-body">
Welcome ${name}!! <a href="/list-todos">Click here</a> to manage your
todo's.
</div>
</div>
</div>
<%@ include file="common/footer.jspf"%>
Todo Page View
<%@ include file="common/header.jspf"%>
<%@ include file="common/navigation.jspf"%>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3 ">
<div class="panel panel-primary">
<div class="panel-heading">Add TODO</div>
<div class="panel-body">
<form:form method="post" modelAttribute="todo">
<form:hidden path="id" />
<fieldset class="form-group">
<form:label path="description">Description</form:label>
<form:input path="description" type="text" class="form-control"
required="required" />
<form:errors path="description" cssClass="text-warning" />
</fieldset>
<fieldset class="form-group">
<form:label path="targetDate">Target Date</form:label>
<form:input path="targetDate" type="text" class="form-control"
required="required" />
<form:errors path="targetDate" cssClass="text-warning" />
</fieldset>
<button type="submit" class="btn btn-success">Save</button>
</form:form>
</div>
</div>
</div>
</div>
</div>
<%@ include file="common/footer.jspf"%>
List Todo Page View
<%@ include file="common/header.jspf"%>
<%@ include file="common/navigation.jspf"%>
<div class="container">
<div>
<a type="button" class="btn btn-primary btn-md" href="/add-todo">Add Todo</a>
</div>
<br>
<div class="panel panel-primary">
<div class="panel-heading">
<h3>List of TODO's</h3>
</div>
<div class="panel-body">
<table class="table table-striped">
<thead>
<tr>
<th width="40%">Description</th>
<th width="40%">Target Date</th>
<th width="20%"></th>
</tr>
</thead>
<tbody>
<c:forEach items="${todos}" var="todo">
<tr>
<td>${todo.description}</td>
<td><fmt:formatDate value="${todo.targetDate}"
pattern="dd/MM/yyyy" /></td>
<td><a type="button" class="btn btn-success"
href="/update-todo?id=${todo.id}">Update</a>
<a type="button" class="btn btn-warning"
href="/delete-todo?id=${todo.id}">Delete</a></td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
<%@ include file="common/footer.jspf"%>
Simple Error Page View
<%@ include file="common/header.jspf"%>
<%@ include file="common/navigation.jspf"%>
<div class="container">
An exception occurred! Please contact Support!
</div>
<%@ include file="common/footer.jspf"%>
Running the Application
- From the root directory of the application and type the following command to run it -
$ mvn spring-boot:run
- From your IDE, run the TodoManagementSpringBoot2Application.main() method as a standalone Java class that will start the embedded Tomcat server on port 8080 and point the browser to http://localhost:8080/.
Demo
1. Login Page
2. Home Page
4. Create Todo
5. Update Todo
6. Logout Page
The source code of this article available on my Github repository at https://github.com/RameshMF/todo-management-spring-boot
Learn Spring Boot in depth on Spring Boot Tutorial
Hello!
ReplyDeleteWhen I try to add a todo I got this error:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'todo' available as request attribut.
What is the problem?
Thank you
Is there a quick how-to on creating the required database and tables, and connecting them to STS? Thanks...
ReplyDeletewhen I run spring-boot:run, getting below error.
ReplyDeletejava.sql.SQLException: Access denied for user 'myuser '@'localhost' (using password: YES)
please help out
Check your database credentials (user name and password). If you still get the issue then follow this post at
DeleteSQLException: Access denied for user ‘root’@’localhost’
Thank you. It's resolved after granting all privileges to the user and making changes to mysqld conf file. Issue was resolved only after restarting the machine after making the fix(es). This issue took lot of time to resolve. Really couldn't make a note of which fix helped.
ReplyDeleteVery nice tutorials. Thank you so much.
Is there an Mysql file attached for creating the required tables to test the application
ReplyDeleteDear All ~,
ReplyDeleteI need your feedback please,
My environment:
- Tool: IntelliJ
- MySQL, JPA, JSP, JSPF, etc
I have two problem:
1. after compile source, aftermatically generated static password (login: user, pas: 68c1d088-0a58-4f1d-99ee-a630e9067de0) when I purposed login and password no accessible. Why?
2. After getting login, coming Whitelabel Error Page
Well appericiated.
use JDK 8. And Create a database before you run the application
DeleteCan you send me the workable source code in zip file
ReplyDeleteThe source code on GitHub repository is stable and it's working for everyone.
Deleteyou should add, please,
ReplyDeleteorg.hibernate
hibernate-core
5.2.12.Final
My code is showing: Field todoRepository in net.guides.springboot.todomanagement.service.TodoService required a bean named 'entityManagerFactory' that could not be found. What to do?
ReplyDeleteCheck for database configuration. If your application not connected to database then this error occurs.
DeleteCan you fix it? Im sure that i have the correct configuration.
DeleteAfter logging in with password as specified the next thing to appear is "The localhost page can't be found". What is the issue?
ReplyDeleteI also getting same error,
Deletegive solution ?
Getting same error.
DeleteGetting error: Description Resource Path Location Type
ReplyDeleteThe declared package "net.guides.todo.todomanagementspringboot2" does not match the expected package "net.guides.springboot.todomanagementspringboot" TodoManagementSpringBoot2ApplicationTests.java /todo-management-spring-boot/src/test/java/net/guides/springboot/todomanagementspringboot line 1 Java Problem