Spring Boot 3 MVC: A Step-by-Step Tutorial

In this tutorial we will learn how to create an application based on the Spring MVC pattern, and the Template engine Thymeleaf to render the page View.

This tutorial will guide you through building a simple Spring Boot 3 MVC application. The application will manage customer records with the ability to add, update, list, and delete customers. The frontend will use Thymeleaf templates for rendering the views.

Prerequisites

  • Java 17+
  • Maven 3.6+
  • An IDE like IntelliJ IDEA or Eclipse
  • Basic knowledge of Spring Boot and Thymeleaf

Setting Up the Spring Boot MVC Project

Start by creating a new Spring Boot project using Spring Initializr or your IDE. Include the following dependencies:

  • Spring Web: For building the web layer.
  • Thymeleaf: For rendering the views.
  • Spring Data JPA: For data persistence.
  • H2 Database: For an in-memory database (you can replace this with MySQL, PostgreSQL, etc., if needed).

The Model

The Customer class represents the customer entity. It is annotated with @Entity to map it to the database table.

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String surname;
    private String email;
}

The Repository

The repository interface extends JpaRepository to provide CRUD operations on the Customer entity. 

import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

The Service

The service layer encapsulates business logic. It uses the repository to interact with the database:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class CustomerServiceImpl implements ICustomerService {

    @Autowired
    private CustomerRepository repo;

    @Override
    public Customer save(Customer customer) {
        return repo.save(customer);
    }

    @Override
    public List<Customer> findAll() {
        return repo.findAll();
    }

    @Override
    public Customer getCustomerById(Long id) {
        Optional<Customer> opt = repo.findById(id);
        if (opt.isPresent()) {
            return opt.get();
        } else {
            throw new CustomerNotFoundException("Customer with Id : " + id + " Not Found");
        }
    }

    @Override
    public void deleteCustomerById(Long id) {
        repo.delete(getCustomerById(id));
    }

    @Override
    public void update(Customer customer) {
        repo.save(customer);
    }
}

The Controller

The controller handles HTTP requests, interacts with the service layer, and returns the appropriate views.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/customer")
public class CustomerController {

    @Autowired
    private ICustomerService service;

    @GetMapping("/")
    public String showHomePage() {
        return "homePage";
    }

    @GetMapping("/register")
    public String showRegistration() {
        return "registerCustomerPage";
    }

    @PostMapping("/save")
    public String saveCustomer(@ModelAttribute Customer customer, Model model) {
        Long id = service.save(customer).getId();
        String message = "Record with id : '" + id + "' is saved successfully!";
        model.addAttribute("message", message);
        return "registerCustomerPage";
    }

    @GetMapping("/getAllCustomers")
    public String getAllCustomers(@RequestParam(value = "message", required = false) String message, Model model) {
        List<Customer> customers = service.findAll();
        model.addAttribute("list", customers);
        model.addAttribute("message", message);
        return "allCustomersPage";
    }

    @GetMapping("/edit")
    public String getEditPage(Model model, RedirectAttributes attributes, @RequestParam Long id) {
        try {
            Customer customer = service.getCustomerById(id);
            model.addAttribute("customer", customer);
            return "editCustomerPage";
        } catch (CustomerNotFoundException e) {
            attributes.addAttribute("message", e.getMessage());
            return "redirect:getAllCustomers";
        }
    }

    @PostMapping("/update")
    public String updateCustomer(@ModelAttribute Customer customer, RedirectAttributes attributes) {
        service.update(customer);
        attributes.addAttribute("message", "Customer with id: '" + customer.getId() + "' is updated successfully!");
        return "redirect:getAllCustomers";
    }

    @GetMapping("/delete")
    public String deleteCustomer(@RequestParam Long id, RedirectAttributes attributes) {
        try {
            service.deleteCustomerById(id);
            attributes.addAttribute("message", "Customer with Id : '" + id + "' is removed successfully!");
        } catch (CustomerNotFoundException e) {
            attributes.addAttribute("message", e.getMessage());
        }
        return "redirect:getAllCustomers";
    }
}

The Views

Within the src/main/resources/templates we will be adding the views for our Spring MVC application. We will add the following HTML pages:

├── resources/
│   ├── static/
│   │   │   └── css/
│   │   ├── templates/
│   │   │   ├── allCustomersPage.html
│   │   │   ├── editCustomerPage.html
│   │   │   ├── homePage.html
│   │   │   └── registerCustomerPage.html
│   │   └── application.properties

The Home page

The homePage.html is the landing page of our application. When we request the Root Context for the application (“/”) the Controller will redirect to this page:

<head>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" />
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
</head>

<body>
	<div class="col-5">
		<div class="container">

			<div class="card">
				<div class="card-header bg-info text-center text-white">
					<h3>Spring Boot MVC Example</h3>
				</div>
				<div class="card-body">
					<form>
						<a th:href="@{/customer/register}" class="btn btn-success ">Add Customer <i
								class="fa fa-plus-square" aria-hidden="true"></i></a>
						<a th:href="@{/customer/getAllCustomers}" class="btn btn-primary">Show All Customers</a>
					</form>
				</div>
			</div>
		</div>
	</div>
</body>

</html>

Here is how to page will look when you request the customer endpoint (http://localhost:8080/customer):

Spring boot 3 MVC tutorial

The Add Customer View

By Clicking on the “Add Customer” button, the navigation will continue in the registerCustomerPage.html View which contains a Form to insert a new Customer:

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org/">

<head>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" />
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
	<style>
		.label-column {
            padding-right: 15px;
        }
	</style>
</head>

<body>
<div class="col-5">
	<div class="container">

		<div class="card">
			<div class="card-header bg-info text-center text-white">
				<h3>Add Customer</h3>
			</div>
			<div class="card-body">
				<form th:action="@{/customer/save}" method="POST" id="invoiceForm">
					<div class="row">
						<div class="col-2 label-column">
							<label for="name"><b>NAME</b></label>
						</div>
						<div class="col-4">
							<input type="text" name="name" id="name" class="form-control" />
						</div>
					</div> <br />
					<div class="row">
						<div class="col-2 label-column">
							<label for="surname"><b>SURNAME</b></label>
						</div>
						<div class="col-4">
							<input type="text" name="surname" id="surname" class="form-control" />
						</div>
					</div><br />
					<div class="row">
						<div class="col-2 label-column">
							<label for="email"><b>EMAIL</b></label>
						</div>
						<div class="col-4">
							<input type="text" name="email" id="email" class="form-control" />
						</div>
					</div> <br />
					<button type="submit" class="btn btn-success">Save Customer <i class="fa fa-plus-square"
																				  aria-hidden="true"></i></button>
					<a th:href="@{/customer/getAllCustomers}" class="btn btn-primary">Show All Customers</a>
				</form>
			</div>
			<div th:if="${message!=null}" class="card-footer bg-white text-info">
				<span th:text="${message}"></span>
			</div>
		</div>
	</div>
</div>
</body>

</html>

Here is our Add Customer View in Action:

spring boot mvc getting started

The List Customer View

To list the Customers in a tabular style we can use the following View (allCustomersPage.html) which will loop through the Customer. Within it, the th:each loop is a Thymeleaf attribute used to iterate over a collection of objects and display them in the HTML template. In this specific context, it’s used to iterate over a list of Customer objects and render each one as a row in an HTML table.

<html xmlns:th="https://www.thymeleaf.org/">

<head>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" />
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
</head>

<body>
	<div class="col-5">
		<div class="container">
			<div class="card">
				<div class="card-header bg-info text-center text-white">
					<h3>Customer List</h3>
				</div>
				<div class="card-body">
					<table class="table table-hover">
						<tr class="bg-dark text-white">
							<th>ID</th>
							<th>Name</th>
							<th>Surname</th>
							<th>Email</th>
							<th>Edit/Delete</th>
						</tr>
						<tr th:each="ob:${list}">
							<td th:text=${ob.id}></td>
							<td th:text=${ob.name}></td>
							<td th:text=${ob.surname}></td>
							<td th:text=${ob.email}></td>
							<td><a th:href="@{/customer/delete(id=${ob.id})}" class="btn btn-danger">DELETE <i
										class="fa fa-trash-o" aria-hidden="true"></i></a> |
								<a th:href="@{/customer/edit(id=${ob.id})}" class="btn btn-warning">EDIT <i
										class="fa fa-pencil-square-o" aria-hidden="true"></i></a>
							</td>
						</tr>
					</table>
					<a th:href="@{/customer/register}" class="btn btn-success ">Add Customer <i class="fa fa-plus-square"
							aria-hidden="true"></i></a>
					<a th:href="@{/customer/}" class="btn btn-primary">Go to Home</a>
				</div>
				<div class="card-footer bg-white text-success" th:if="${message!=null}">
					<span th:text="${message}"></span>
				</div>
			</div>
		</div>
	</div>
</body>

</html>

Here is the Customer List View in Action:

spring boot mvc example

The Edit Customer View

For the sake of completeness, we will also include the editCustomer.html page which allows editing an existing Customer:

<html xmlns:th="https://www.thymeleaf.org/">

<head>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" />
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
</head>

<body>
	<div class="col-5">
		<div class="container">
			<div class="card">
				<div class="card-header bg-primary text-white text-center">
					<h3>Edit Customer</h3>
				</div>
				<div class="card-body">
					<form th:action="@{/customer/update}" method="POST" id="invoiceForm" th:object="${customer}">
						<div class="row">
							<div class="col-2">
								<label for="id">ID</label>
							</div>
							<div class="col-4">
								<input type="text" th:field="*{id}" class="form-control" readonly />
							</div>
						</div><br />
						<div class="row">
							<div class="col-2">
								<label for="name">NAME</label>
							</div>
							<div class="col-4">
								<input type="text" th:field="*{name}" class="form-control" />
							</div>
						</div><br />
						<div class="row">
							<div class="col-2">
								<label for="location">SURNAME</label>
							</div>
							<div class="col-4">
								<input type="text" th:field="*{surname}" class="form-control" />
							</div>
						</div><br />
						<div class="row">
							<div class="col-2">
								<label for="amount">EMAIL</label>
							</div>
							<div class="col-4">
								<input type="text" th:field="*{email}" class="form-control" />
							</div>
						</div> <br />
						<button type="submit" class="btn btn-success">Update <i class="fa fa-wrench"
								aria-hidden="true"></i></button>
						<a th:href="@{/customer/getAllCustomers}" class="btn btn-primary">Show All Customers</a>
					</form>
				</div>
				<div class="card-footer"></div>
			</div>
		</div>
	</div>
</body>

</html>

We are done. You can build the application and access it at http://localhost:8080/customer/

Source code: https://github.com/fmarchioni/masterspringboot/tree/master/front-ends/spring-mvc

Conclusion

This tutorial covered the basics of building a Spring Boot 3 MVC application with Thymeleaf for the view layer. You learned how to:

  • Create a Customer entity.
  • Implement the repository and service layers.
  • Build a controller to handle CRUD operations.
  • Design views using Thymeleaf to interact with the customer data.

You can extend this project by adding more features like validation, error handling, and integrating with a persistent database like MySQL or PostgreSQL.

Found the article helpful? if so please follow us on Socials
Twitter Icon       Facebook Icon       LinkedIn Icon       Mastodon Icon