Introduction
Form Data Validation is a very common, and rudimentary step in building any web application with user input. We want to make sure that certain ranges are respected, and that certain formats are followed. For example, we'll want to make sure that the user isn't -345
years old, or that their email address is valid.
There are many ways to validate form data - and the method you use depends on your application. In general, you'll want to perform client-side validation, as well as server-side validation. Client-side validation makes sure that unfiltered data doesn't even reach the back-end, while server-side validation makes sure that the wrong data isn't processed further.
In this article, we'll cover how to perform form data validation in Spring Boot with Thymeleaf, as the template engine.
We'll leverage Spring Boot's built-in Bean Validation API, which makes this process simple and straightforward.
Spring Boot Validation Maven Dependency
Spring Boot allows us to define validation criteria using annotations. In your Domain Model, you can simply annotate fields with the constraints, and it'll enforce them.
For the validation annotations to work we need to add the following dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Domain Model
Let's define a simple POJO, with a few fields that we want to validate:
@Entity
public class Person {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
@NotEmpty
@Size(min = 5)
private String fullName;
@NotEmpty
@Email
private String email;
@NotNull
@Min(value = 18)
private Integer age;
// Getters and Setters
}
Now, let's break the annotations we used:
@NotEmpty
- is used to constrain a field of typeString
,Collection
,Map
, orArray
to not benull
or empty.@Size([min = x, max = y])
- is used to define the rules for the size of aString
,Collection
,Map
, orArray
.@Email
- helps us to validate the string against a regex which defines the structure of a valid email.@NotNull
- tells Spring the field should not be null, but it can be empty.@Min
and@Max
are used to specify the limits of a variable. For example, the@Min
age could be set to, say, 18.
Persistence Layer - Repository
To create a simple CRUD repository, all we have to do is extend the JpaRepository
and supply our domain model and the ID's data type:
@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}
Creating a Form with Thymeleaf
Now, let's make a simple form using HTML and Bootstrap to collect information:
<form th:action="@{/add}" th:object="${person}" method="post" class="form">
<div class="form-group">
<label for="fullName">Name</label>
<input class="form-control" type="text" th:field="*{fullName}" id="fullName" placeholder="Full Name">
<div class="alert alert-warning" th:if="${#fields.hasErrors('fullName')}" th:errors="*{fullName}"></div>
</div>
<div class="form-group">
<label for="email">Email</label>
<input class="form-control" type="text" th:field="*{email}" id="email" placeholder="Email">
<div class="alert alert-warning" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
</div>
<div class="form-group">
<label for="age">Age</label>
<input class="form-control" type="text" th:field="*{age}" id="age" placeholder="Age">
<div class="alert alert-warning" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></div>
</div>
<input type="submit" class="btn btn-success" value="Add User">
</form>
The form points to /add
and sends a POST
request. The fields in our object, fullName
, age
and email
are in the form, signified by th:field
. Since we've got our th:object=${person}
,we can refer to this person
object by substituting it with a *
before the fields.
*{fullName}
is the same as ${person.fullName}
. Each input also has a hidden <div>
which is shown only if the ${#fields.hasErrors()}
calls evaluate to true
. If there aren't errors, this div doesn't exist. If there are, the th:errors
tag lets us specify a message. If we simply pass in the field that causes the error, such as th:errors="*{age}"
, the default message from the Bean Validator API are used, for that field.
This results in a form that looks like this:
Since these messages aren't very user-friendly, we'll want to customize them by supplying our own messages.
Controller
Now, let's make a controller that'll handle a request to save a Person
into the database. As usual, we'll have a @GetMapping()
to show the form, and a @PostMapping
to handle the request. In the method signature of the @PostMapping
, we'll annotate the POJO with @Valid
.
The @Valid
annotation triggers the Bean Validator to check if the fields populated in the object conform to the annotations we've used in the class definition. If you don't use the @Valid
annotation, it won't check anything, and even values you might not expect can be populated in the object.
In our case, the Person person
object is the object that's populated with the inputs of a form:
@GetMapping("/add")
public String showAddPersonForm(Person person) {
return "add-person";
}
@PostMapping("/add")
public String addPerson(@Valid Person person, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-person";
}
repository.save(person);
return "redirect:/index";
}
If there are any issues with this Person
, the ${fields}
attribute will have errors within it, tied to the field that caused the error.
Customizing Error Messages
To set a custom message for any validation constraint, you can use the message
option:
@NotEmpty(message = "Field can't be empty!)
private String field;
You can simply write these messages out in your model, like this. Though, it's considered good practice to put the validation messages in a properties
file, that you can reference. This is done since you can bundle all validation messages together, and can update them if the models get changed at a later date.
Let's make our ValidationMessages.properties
under src/main/resources
:
Size.Person.FullName=The Full Name should have at least 5 characters
We will then modify the Person
model, and supply this property as the message
of the @Size
annotation:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
@NotEmpty(message = "The Full Name can't be null")
@Size(min = 5, message = "{Size.Person.FullName}")
private String fullName;
Let's go back to our form and see how it looks now:
Conclusion
In this article, we've gone over how to use the Bean Validator API that Spring Boot uses to effortlessly perform form data validation with Thymeleaf.
We've created a domain model, annotated our fields with Bean Validator constraints. Then, we've made a repository and controller to display the form and process it, as well as save the relevant information into a Person
object, making sure that the fields are validated with the @Valid
annotation.
You can find the source code on GitHub.