Spring Boot Tutorial - Build Employee Management Project from Scratch using Spring Boot + Spring Security + Thymeleaf and MySQL Database

In this tutorial, we will build an Employee Management System project from scratch using Spring Boot 3, Spring MVC, Spring Security 6, Thymeleaf, and MySQL. This comprehensive guide will take you through all the essential steps needed to create a fully functional web application.

Overview

The Employee Management System project will implement the following features:

  • List Employees
  • Add Employee
  • Update Employee
  • Delete Employee
  • Pagination
  • Sorting
  • User Login
  • User Registration
  • User Logout

Tools and Technologies Used

  • Java 17+
  • Spring Boot 3
  • Spring Data JPA (Hibernate)
  • Spring Security 6
  • MySQL
  • Eclipse STS
  • Maven
  • Tomcat
  • Thymeleaf

Step 1: Create Spring Boot Project

Spring Boot provides a web tool called Spring Initializer to bootstrap an application quickly. Follow these steps:

Go to Spring Initializer

Visit https://start.spring.io/.

Use the following details:

  • Project Name: employee-management-webapp
  • Project Type: Maven
  • Dependencies: Spring Web, Spring Data JPA, MySQL Driver, Spring Security, Thymeleaf
  • Package name: net.javaguides.springboot

Step 2: Add Maven Dependencies

Here is the complete pom.xml file for your reference:

        <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-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</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>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql.com</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

Step 3: Configure MySQL Database

Spring Boot tries to auto-configure a DataSource if the spring-boot-starter-data-jpa dependency is in the classpath by reading the database configuration from the application.properties file.

Configure application.properties

Open the application.properties file and add the following properties:

# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)

spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root
spring.datasource.password=root


# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE

You will need to create a database named demo in MySQL, and change the spring.datasource.username and spring.datasource.password properties as per your MySQL installation.

Spring Boot uses Hibernate as the default JPA implementation. The property spring.jpa.hibernate.ddl-auto is used for database initialization. We’ve used the value update for this property.

Step 4: Create Models

Let's create models or entities for our Employee Management System application.

Create the Employee Class

Create an Employee class inside the model package:

package net.javaguides.springboot.model;

import jakarta.persistence.*;

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    // Getters and Setters
}

Create the User Class

Create a User class inside the model package:

package net.javaguides.springboot.model;

import java.util.Collection;
import jakarta.persistence.*;

@Entity
@Table(name =  "user", uniqueConstraints = @UniqueConstraint(columnNames = "email"))
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    private String email;

    private String password;

    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(
        name = "users_roles",
        joinColumns = @JoinColumn(
            name = "user_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(
            name = "role_id", referencedColumnName = "id"))
    private Collection<Role> roles;

    // Getters and Setters
}

Create the Role Class

Create a Role class inside the model package:

package net.javaguides.springboot.model;

import jakarta.persistence.*;

@Entity
@Table(name = "role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    // Getters and Setters
}

Step 5: Create Repository Layer

We will create JPA repositories to access data from the database.

Create EmployeeRepository

Create an EmployeeRepository interface inside the repository package:

package net.javaguides.springboot.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import net.javaguides.springboot.model.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Create UserRepository

Create a UserRepository interface inside the repository package:

package net.javaguides.springboot.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import net.javaguides.springboot.model.User;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);
}

Step 6: Create Service Layer

We will implement a service layer.

Create EmployeeService Interface

Create an EmployeeService interface inside the service package:

package net.javaguides.springboot.service;

import java.util.List;
import org.springframework.data.domain.Page;
import net.javaguides.springboot.model.Employee;

public interface EmployeeService {
    List<Employee> getAllEmployees();
    void saveEmployee(Employee employee);
    Employee getEmployeeById(long id);
    void deleteEmployeeById(long id);
    Page<Employee> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection);
}

Create EmployeeServiceImpl Class

Create an EmployeeServiceImpl class inside the service package:

package net.javaguides.springboot.service;

import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @Override
    public void saveEmployee(Employee employee) {
        this.employeeRepository.save(employee);
    }

    @Override
    public Employee getEmployeeById(long id) {
        Optional<Employee> optional = employeeRepository.findById(id);
        Employee employee = null;
        if (optional.isPresent()) {
            employee = optional.get();
        } else {
            throw new RuntimeException(" Employee not found for id :: " + id);
        }
        return employee;
    }

    @Override
    public void deleteEmployeeById(long id) {
        this.employeeRepository.deleteById(id);
    }

    @Override
    public Page<Employee> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection) {
        Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortField).ascending() :
            Sort.by(sortField).descending();

        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort);
        return this.employeeRepository.findAll(pageable);
    }
}

Create UserService Interface

Create a UserService interface inside the service package:

package net.javaguides.springboot.service;

import org.springframework.security.core.userdetails.UserDetailsService;
import net.javaguides.springboot.dto.UserRegistrationDto;
import net.javaguides.springboot.model.User;

public interface UserService extends UserDetailsService {
    User save(UserRegistrationDto registrationDto);
}

Create UserServiceImpl Class

Create a UserServiceImpl class inside the service package:

package net.javaguides.springboot.service;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import net.javaguides.springboot.dto.UserRegistrationDto;
import net.javaguides.springboot.model.Role;
import net.javaguides.springboot.model.User;
import net.javaguides.springboot.repository.UserRepository;

@Service
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    public UserServiceImpl(UserRepository userRepository) {
        super();
        this.userRepository = userRepository;
    }

    @Override
    public User save(UserRegistrationDto registrationDto) {
        User user = new User(registrationDto.getFirstName(),
                registrationDto.getLastName(), registrationDto.getEmail(),
                passwordEncoder.encode(registrationDto.getPassword()), Arrays.asList(new Role("ROLE_USER")));

        return userRepository.save(user);
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userRepository.findByEmail(username);
        if (user == null) {
            throw new UsernameNotFoundException("Invalid username or password.");
        }
        return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), mapRolesToAuthorities(user.getRoles()));
    }

    private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection<Role> roles){
        return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
    }
}

Step 7: Create DTO

Create UserRegistrationDto Class

Create a UserRegistrationDto class inside the dto package:

package net.javaguides.springboot.dto;

public class UserRegistrationDto {
    private String firstName;
    private String lastName;
    private String email;
    private String password;

    public UserRegistrationDto() {
    }

    public UserRegistrationDto(String firstName, String lastName, String email, String password) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.password = password;
    }

    // Getters and Setters
}

Step 8: Spring Security Configuration

Create SecurityConfiguration Class

Create a SecurityConfiguration class inside the config package:

package net.javaguides.springboot.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .requestMatchers("/registration**", "/js/**", "/css/**", "/img/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin((form) -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout((logout) -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            );
        return http.build();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}

Let's understand above Spring Security code, we defined a SecurityConfiguration class for setting up security in a Spring Boot application using Spring Security 6.3. Let's break down each part of the code:

Package and Imports

package net.javaguides.springboot.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
  • Package Declaration: Declares the package for the configuration class.
  • Imports: Imports the necessary Spring Security and Spring Framework classes and interfaces.

Class and Annotations

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
  • @Configuration: Indicates that this class contains Spring configuration.
  • @EnableWebSecurity: Enables Spring Security's web security support and provides Spring MVC integration.

Fields

@Autowired
private UserDetailsService userDetailsService;
  • userDetailsService: A service that provides the UserDetails for authentication. It is injected by Spring using the @Autowired annotation.

Beans

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
  • BCryptPasswordEncoder: A bean that provides a password encoder using the BCrypt hashing function, which is a strong and secure way to store passwords.

Security Filter Chain

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((requests) -> requests
            .requestMatchers("/registration**", "/js/**", "/css/**", "/img/**").permitAll()
            .anyRequest().authenticated()
        )
        .formLogin((form) -> form
            .loginPage("/login")
            .permitAll()
        )
        .logout((logout) -> logout
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login?logout")
            .permitAll()
        );
    return http.build();
}
  • SecurityFilterChain: Defines the security filter chain.
  • HttpSecurity: Allows configuring web-based security for specific http requests.
    • authorizeHttpRequests: Configures authorization for HTTP requests.
      • requestMatchers: Specifies the URL patterns that should be permitted without authentication.
      • anyRequest().authenticated(): Requires authentication for any other requests.
    • formLogin: Configures form-based authentication.
      • loginPage("/login"): Specifies the custom login page URL.
      • permitAll(): Allows access to the login page for all users.
    • logout: Configures logout functionality.
      • logoutUrl("/logout"): Specifies the logout URL.
      • logoutSuccessUrl("/login?logout"): Redirects to the login page with a logout message after logout.
      • permitAll(): Allows access to the logout functionality for all users.

Global Authentication Configuration

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
  • configureGlobal: Configures the global authentication manager.
    • userDetailsService: Sets the custom UserDetailsService for authentication.
    • passwordEncoder: Sets the BCryptPasswordEncoder for encoding passwords.

Step 9: Create Controller Layer

Let's create the controller layer to handle web requests.

9.1 Create EmployeeController Class

Create the EmployeeController class inside the controller package:

package net.javaguides.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.service.EmployeeService;

import java.util.List;

@Controller
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/")
    public String viewHomePage(Model model) {
        return findPaginated(1, "firstName", "asc", model);        
    }

    @GetMapping("/showNewEmployeeForm")
    public String showNewEmployeeForm(Model model) {
        Employee employee = new Employee();
        model.addAttribute("employee", employee);
        return "new_employee";
    }

    @PostMapping("/saveEmployee")
    public String saveEmployee(@ModelAttribute("employee") Employee employee) {
        employeeService.saveEmployee(employee);
        return "redirect:/";
    }

    @GetMapping("/showFormForUpdate/{id}")
    public String showFormForUpdate(@PathVariable(value = "id") long id, Model model) {
        Employee employee = employeeService.getEmployeeById(id);
        model.addAttribute("employee", employee);
        return "update_employee";
    }

    @GetMapping("/deleteEmployee/{id}")
    public String deleteEmployee(@PathVariable(value = "id") long id) {
        employeeService.deleteEmployeeById(id);
        return "redirect:/";
    }

    @GetMapping("/page/{pageNo}")
    public String findPaginated(@PathVariable(value = "pageNo") int pageNo,
            @RequestParam("sortField") String sortField,
            @RequestParam("sortDir") String sortDir,
            Model model) {
        int pageSize = 5;

        Page<Employee> page = employeeService.findPaginated(pageNo, pageSize, sortField, sortDir);
        List<Employee> listEmployees = page.getContent();

        model.addAttribute("currentPage", pageNo);
        model.addAttribute("totalPages", page.getTotalPages());
        model.addAttribute("totalItems", page.getTotalElements());

        model.addAttribute("sortField", sortField);
        model.addAttribute("sortDir", sortDir);
        model.addAttribute("reverseSortDir", sortDir.equals("asc") ? "desc" : "asc");

        model.addAttribute("listEmployees", listEmployees);
        return "index";
    }
}

9.2 Create UserRegistrationController Class

Create the UserRegistrationController class inside the controller package:

package net.javaguides.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import net.javaguides.springboot.dto.UserRegistrationDto;
import net.javaguides.springboot.service.UserService;

@Controller
@RequestMapping("/registration")
public class UserRegistrationController {

    @Autowired
    private UserService userService;

    @ModelAttribute("user")
    public UserRegistrationDto userRegistrationDto() {
        return new UserRegistrationDto();
    }

    @GetMapping
    public String showRegistrationForm() {
        return "registration";
    }

    @PostMapping
    public String registerUserAccount(@ModelAttribute("user") UserRegistrationDto registrationDto) {
        userService.save(registrationDto);
        return "redirect:/registration?success";
    }
}

9.3 Create MainController Class

Create the MainController class inside the controller package:

package net.javaguides.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MainController {
    
    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

Step 10: Create Thymeleaf Templates

10.1 Create index.html

Create an index.html file inside the src/main/resources/templates directory:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="ISO-8859-1">
    <title>Employee Management System</title>
    <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
</head>
<body>
<div class="container">
    <h2>Employee Management System</h2>
    <div th:if="${employees != null}">
        <a class="btn btn-primary mb-3" th:href="@{/showNewEmployeeForm}">Add Employee</a>
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th th:click="|@{/page/1(sortField='firstName',sortDir=${reverseSortDir})}|">First Name</th>
                    <th th:click="|@{/page/1(sortField='lastName',sortDir=${reverseSortDir})}|">Last Name</th>
                    <th th:click="|@{/page/1(sortField='email',sortDir=${reverseSortDir})}|">Email</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="employee : ${listEmployees}">
                    <td th:text="${employee.firstName}">First Name</td>
                    <td th:text="${employee.lastName}">Last Name</td>
                    <td th:text="${employee.email}">Email</td>
                    <td>
                        <a th:href="@{/showFormForUpdate/{id}(id=${employee.id})}">Edit</a> | 
                        <a th:href="@{/deleteEmployee/{id}(id=${employee.id})}">Delete</a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <div>
        <ul class="pagination">
            <li th:if="${currentPage > 1}">
                <a class="page-link" th:href="@{/page/{pageNo}(pageNo=${currentPage - 1})}">Previous</a>
            </li>
            <li th:each="i : ${#numbers.sequence(1, totalPages)}"
                th:classappend="${i == currentPage} ? 'active'">
                <a class="page-link" th:href="@{/page/{pageNo}(pageNo=${i})}" th:text="${i}">1</a>
            </li>
            <li th:if="${currentPage < totalPages}">
                <a class="page-link" th:href="@{/page/{pageNo}(pageNo=${currentPage + 1})}">Next</a>
            </li>
        </ul>
    </div>
</div>
</body>
</html>

10.2 Create new_employee.html

Create a new_employee.html file inside the src/main/resources/templates directory:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="ISO-8859-1">
    <title>Employee Management System</title>
    <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
</head>
<body>
<div class="container">
    <h2>Add Employee</h2>
    <form th:action="@{/saveEmployee}" th:object="${employee}" method="post">
        <div class="form-group">
            <label>First Name</label>
            <input type="text" class="form-control" th:field="*{firstName}" placeholder="First Name"/>
        </div>
        <div class="form-group">
            <label>Last Name</label>
            <input type="text" class="form-control" th:field="*{lastName}" placeholder="Last Name"/>
        </div>
        <div class="form-group">
            <label>Email</label>
            <input type="email" class="form-control" th:field="*{email}" placeholder="Email"/>
        </div>
        <button type="submit" class="btn btn-primary">Save</button>
    </form>
</div>
</body>
</html>

10.3 Create update_employee.html

Create an update_employee.html file inside the src/main/resources/templates directory:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="ISO-8859-1">
    <title>Employee Management System</title>
    <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
</head>
<body>
<div class="container">
    <h2>Update Employee</h2>
    <form th:action="@{/saveEmployee}" th:object="${employee}" method="post">
        <div class="form-group">
            <label>First Name</label>
            <input type="text" class="form-control" th:field="*{firstName}" placeholder="First Name"/>
        </div>
        <div class="form-group">
            <label>Last Name</label>
            <input type="text" class="form-control" th:field="*{lastName}" placeholder="Last Name"/>
        </div>
        <div class="form-group">
            <label>Email</label>
            <input type="email" class="form-control" th:field="*{email}" placeholder="Email"/>
        </div>
        <button type="submit" class="btn btn-primary">Save</button>
    </form>
</div>
</body>
</html>

10.4 Create registration.html

Create a registration.html file inside the src/main/resources/templates directory:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="ISO-8859-1">
    <title>User Registration</title>
    <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
</head>
<body>
<div class="container">
    <h2>User Registration</h2>
    <form th:action="@{/registration}" th:object="${user}" method="post">
        <div class="form-group">
            <label>First Name</label>
            <input type="text" class="form-control" th:field="*{firstName}" placeholder="First Name"/>
        </div>
        <div class="form-group">
            <label>Last Name</label>
            <input type="text" class="form-control" th:field="*{lastName}" placeholder="Last Name"/>
        </div>
        <div class="form-group">
            <label>Email</label>
            <input type="email" class="form-control" th:field="*{email}" placeholder="Email"/>
        </div>
        <div class="form-group">
            <label>Password</label>
            <input type="password" class="form-control" th:field="*{password}" placeholder="Password"/>
        </div>
        <button type="submit" class="btn btn-primary">Register</button>
    </form>
</div>
</body>
</html>

10.5 Create login.html

Create a login.html file inside the src/main/resources/templates directory:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="ISO-8859-1">
    <title>Login</title>
    <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
</head>
<body>
<div class="container">
    <h2>Login</h2>
    <form th:action="@{/login}" method="post">
        <div th:if="${param.error}">
            <div class="alert alert-danger">Invalid username or password.</div>
        </div>
        <div th:if="${param.logout}">
            <div class="alert alert-info">You have been logged out.</div>
        </div>
        <div class="form-group">
            <label>Email</label>
            <input type="email" class="form-control" name="username" placeholder="Email" required autofocus/>
        </div>
        <div class="form-group">
            <label>Password</label>
            <input type="password" class="form-control" name="password" placeholder="Password" required/>
        </div>
        <button type="submit" class="btn btn-primary">Login</button>
    </form>
    <div>
        <span>New user? <a th:href="@{/registration}">Register here</a></span>
    </div>
</div>
</body>
</html>

Step 11: Run the Spring Boot Application

We’ve successfully built our employee management system application. Let's run our Spring Boot application and test it.

Navigate to the root directory of the application and run the following command:

$ mvn spring-boot:run

The application will start at Spring Boot’s default Tomcat port 8080.

Step 12: YouTube Video Series

You can develop this project from scratch using line-by-line coding from my YouTube videos.

Employee Management Module

Registration and Login Module

Conclusion

This tutorial demonstrated how to create a complete Employee Management System using Spring Boot 3, Spring MVC, Spring Security 6, Thymeleaf, and MySQL. We covered everything from setting up the Spring Boot project, configuring Spring Security, creating the service and repository layers, and building the Thymeleaf templates. By following this guide, you can build a robust and secure employee management application.

Comments