hello! I'm Marisabel!
Category: learning to code

Spring Boot 2.6.5 form Validation with Thymeleaf

It took me a while to get validation to work with Spring Boot, while with Spring MVC it was pretty straight forward. If you are using a different version of Spring, you need to know that some things do change, and this causes confusion when you are not following every single update.

With Spring Boot 2.6.5, I will be honest and say, I have no idea why it was not working, and now it is. However, I would like to record here what worked.

I added this dependency in addition to the spring-boot-starter-validation:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>

The rest of the code was always the same, and it worked. I removed that dependency to see if it would break, but it still did not. 🤷‍♀️

Step 1: Project set up

Create a project with spring initializr.

Step 2: Dependencies

Add the following dependencies:

  • Spring Web WEB
  • Lombok DEVELOPER TOOLS
  • Validation I/O
<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

Step 3: DTO class

Using the annotation @Data at class level from the Lombok library, we save ourselves a lot of typing. This will take care of most methods needed, not limited to setter, getter and toString.

import lombok.Data;

@Data
public class Icecream {

    @NotNull(message = "What is your name?")
    @NotEmpty(message = "What is your name?")
    private String name;

    @NotNull(message = "What is your favorite icecream?")
    @NotEmpty(message = "What is your favorite icecream?")
    private String icecream;
    
}

Step 4: Controller class

Note that on the controller, the @Valid annotation needs to always precede the object we are validating and BindingResults always need to succeed this object. In order to display the errors in our view, we need to make sure we also include the following code:

if ( errors.hasErrors() ) {
            return "index";
        }

This is the full code:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Controller
public class IcecreamController {

@GetMapping( "/" )
 public String getForm(Icecream icecream) {
  return "index";
 }

@PostMapping( "/saveIcecream" )
public String submitForm(@Valid @ModelAttribute("icecream") IceCream icecream, BindingResult errors, Model model) {
       
if ( errors.hasErrors() ) {
            return "index";
        }

else {
    model.addAttribute( "successMsg", "What a delicious flavor! Now go get some!" );
    return "success";
  } } }

Step 5: index page

Under resources/templates/ we add a new HTML file called index.html

The most important line here for the validation to work is :

<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">

Substitute “name” for any of the fields you wish to validate.

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>icecream!</title>

<!-- bootstrap for convenience's sake-->

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">

</head>
<body>
<div align="center" class="col-md-12">
    <h2>What is your favorite flavor of icecream?</h2>
    <div class="col-md-6">

<!-- Form starts -->

<form action="#" th:action="@{/saveIcecream}" th:object="${icecream}" method="post">

<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" id="name" th:field="*{name}">
<p class="alert alert-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">
</div>

<div class="form-group">
<label for="name">Ice cream flavor:</label>
<input type="text" class="form-control" id="icecream" th:field="*{icecream}">
<p class="alert alert-danger" th:if="${#fields.hasErrors('icecream')}" th:errors="*{icecream}">
</div>

<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>

</form>
</div>
</div>
</body>
</html>

Step 6: confirmation page

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Confirmation page</title>

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
</head>
<body>

<div class="col-md-12" align="center">
  <div class="alert alert-success col-md-8" role="alert">
  <p th:text="${successMsg}" />
  </div>

</div>
</body>
</html>

Results:

Initial screen
Errors
Confirmation

Leave a Reply