15.3. Validating Models in a Controller¶
Validation involves both model and controller components of an MVC application. After we have defined validation rules using annotations on the model, we must also update the controller to ensure that the rules are checked and appropriate action is taken when validation fails.
15.3.1. Validation Flow¶
Before diving into the details of the code, let’s consider the logical flow of control for validating data in a request. Recall our POST
handler from the previous chapter, which used model binding to create new Event
objects from form submissions.
@PostMapping("create")
public String processCreateEventForm(@ModelAttribute Event newEvent) {
EventData.add(newEvent);
return "redirect:";
}
The flow of this request can be described as follows:
Server receives
POST
requestServer creates
newEvent
object using request parametersprocessCreateEventForm
is called withnewEvent
newEvent
is savedA 303 redirect response is returned, redirecting the user to
/events
The request creates an Event
object using data from the incoming request. Regardless of what the data looks like, the new object is saved to the data layer. The user could submit an empty form, with no name or description filled in, and our code would be happy to create an Event
and save it. Similarly, a user could submit the full text of the massive novel “War and Peace” as the description. This isn’t great.
Note
Technically, submitting a request containing “War and Peace” would fail with most applications. This is because web servers typically set a limit on the maximum size of a POST
request. However, our application code is willing to take requests of any size, at this point.
Some modest validation rules for a new Event
object might be:
The
name
field must contain between 3 and 20 characters, andThe
description
field may be empty, but may contain no more than 500 characters.
With these rules in place, conceptually, the flow of our controller code should look more like the following:
Server receives
POST
requestServer creates
newEvent
object using request parametersprocessCreateEventForm
is called withnewEvent
Controller checks for validation errors in the model object. If errors are found, return the user to the form. Otherwise, proceed.
newEvent
is savedA 303 redirect response is returned, redirecting the user to
/events
Let’s look at how we can practically do this within Spring Boot.
15.3.2. Handling Validation Errors - Video¶
Note
The starter code for this video is found at the add-validation-annotations branch of the coding-events-demo
repo.
The final code presented in this video is found on the validation-errors branch. As always, code along to the
videos on your own coding-events
project.
15.3.3. Handling Validation Errors - Text¶
When using model binding, we can use Spring Boot annotations and tools to validate new model objects before they are saved to a data layer or database.
15.3.3.1. Using @Valid
¶
Within EventController
, the processCreateEventForm
handler method uses model binding to receive an Event
object that is created using form data. This object is NOT validated automatically, even if validation annotations are present on its fields.
Recall that both the model and controller play a role in validation. The model’s responsibility is simply to define validation rules. The controller must check that those rules are satisfied.
The first step to enable validation in a controller is to add @Valid
to a method parameter that is created using model binding.
@PostMapping("create")
public String processCreateEventForm(@ModelAttribute @Valid Event newEvent) {
EventData.add(newEvent);
return "redirect:";
}
In lieu of explicit validation error handling (which we will cover below), Spring Boot throws an exception if validation fails for the new object. This means that an object that fails validation will NOT be saved.
However, the user experience for this flow is not great. If a user submits bad data, rather than showing them a complicated stack trace, we would be better off to provide a helpful message and allow them to try again.
Note
Remember, exceptions should be messages to programmers and programs, not end users. Even if an exception occurs, we should avoid displaying it to the user.
15.3.3.2. Using the Errors
Object¶
We can prevent a validation exception from being thrown by explicitly handling validation errors. Spring Boot makes an object of type Errors
available when a method uses @Valid
. As with Model
objects, we can access this object by placing it in a method’s parameter list.
33 34 35 36 37 38 39 40 41 42 43 44 | @PostMapping("create")
public String processCreateEventForm(@ModelAttribute @Valid Event newEvent,
Errors errors, Model model) {
if(errors.hasErrors()) {
model.addAttribute("title", "Create Event");
model.addAttribute("errorMsg", "Bad data!");
return "events/create";
}
EventData.add(newEvent);
return "redirect:";
}
|
Here, we have added Errors errors
to our handler. This object has a boolean method, .hasErrors()
that we can use to check for the existence of validation errors. If there are validation errors, we return the form again, along with a simple message for the user. This message can be displayed in the events/create
template by adding some code above the form:
<p th:text="${errorMsg}" style="color:red;"></p>
Now, when a user submits the form with bad data they will be notified and no exception will be thrown. However, the message “Bad data!” is far from ideal. The next section introduces a technique to display more useful error messages.
15.3.4. Check Your Understanding¶
Question
Which of the following statements are true?
A method parameter may have only one annotation.
@Valid
can only be used in conjunction with model binding.Using
@Valid
means that a method will never be called with invalid data.Spring Boot can infer validation requirements based on the name of a field.