Spring - Form Validation

springmvc

// Spring MVC - Form, Form Validation, and View:

@Valid  // Handle validation.  Spring supports validation at both the web level and the 
        // business level.  For each model class, we need to add a separate corresponding
        // validation class.  This class needs to implement the Validator interface, and
        // needs to override the support method, and the validate method.  The
        // MessageCodesResolver class can be used to convert the message code into proper 
        // internationalized message.  Spring automatically validate the model object (or 
        // the command object) wherever it see the @Valid annotation.  The @Valid
        // annotation is added to the controller method, in front of a parameter that
        // is the model instance (not the model map).  For example:
        // public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { ... }

@Size(min = 10, message = "Enter atleast 10 Characters.")
        // This annotation is added above a member variable in a model class.  This is
        // used for validation

@NotNull

@Size(min = 2, max = 14)        // This control the size of a String member variable

@Min(2) // This annotation specifies minimum value for an int member variable

The command object is basically a model object. We use it for binding a form to a model, 
and we also use it to avoid having to add many parameters to our controller methods.

// Form binding and validation:
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<form:form action="/add-todo" method="post" commandName="todo">
    <form:hidden path="id"/>

    <fieldset class="form-group">
        <form:label path="desc">Description</form:label>
        <form:input path="desc" type="text" class="form-control" required="required"/>
        <form:errors path="desc" cssClass="text-warning" />
    </fieldset>
    <button type="submit" class="btn btn-success">Submit</button>
</form:form>

The form:form tag create a form and bind it to a bean. Notice the path attribute. 
It specifies the field in the model object (the command object) that this field represent. 
Also, notice the commandName="todo". This specify the command object. Then we would have 
to make the command object available to the view by adding:

model.addAttribute("todo", new Todo());

to our controller method.

In the above code, we use form:form, form:hidden, form:label, form:input, and
form:errors.  Notice that the error for each field is put below each field itself

@Controller
public class LoginController {
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String showLoginPage() {
        return "login";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String handleUserLogin(ModelMap model, @RequestParam String name,
            @RequestParam String password) {
        model.put("name", name);
        model.put("password", password);
        return "welcome";
    }
}

// Using the ModelMap object to pass information to the view:
public String handleUserLogin(ModelMap model, @RequestParam String name,
        @RequestParam String password) {
    model.put("name", name);
    model.put("password", password);
    return "welcome";
}

In the above code, the first parameter to the handleUserLogin controller
method is a ModelMap instance.  The second and third parameters are request parameters.
We then put this information into the model map instance.  The view can access this
information via ${name}

View files (JSPs) should be kept under: /src/main/webapp/WEB-INF/views

To iterate over a list:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:forEach items="${todos}" var="todo">
    <tr>
        <td>${todo.desc}</td>
        <td>${todo.targetDate}</td>
        <td>${todo.done}</td>
    </tr>
</c:forEach>
<c:out value="${username}"></c:out>
<c:out value="${email}"></c:out>

<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

What is the purpose of the "command object"?

The command object is basically a model object. We use it for binding a form to a model, and we also use it to avoid having to add many parameters to our controller methods.

How can we do form binding with Spring?

Add the Spring Framework Tag Lib to our JSP file:

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<form:form action="/add-todo" method="post" commandName="todo">
    <fieldset class="form-group">
        <form:label path="desc">Description</form:label>
        <form:input path="desc" type="text" class="form-control" required="required"/>
    </fieldset>
</form:form>

The form:form tag create a form and bind it to a bean. Notice the path attribute. It specifies the field in the model object (the command object) that this field represent. Also, notice the commandName="todo". This specify the command object. Then we would have to make the command object available to the view by adding:

model.addAttribute("todo", new Todo());

to our controller method.

What is the purpose of the path attribute in a form:input tag?

It specifies the field in the model object that this field represent.

How can we use the @Size annotation?

@Size(min = 10, message = "Enter atleast 10 Characters.")
private String desc;

Notice that the @Size annotation is added for the property.

What do we need to add to our pom.xml in order to use Hibernate Validator?

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.0.2.Final</version>
</dependency>

What is the purpose of the @Valid annotation?

Spring automatically validate the model object (or the command object) wherever it see the @Valid annotation.

How can we access the result of the validation?

We have to use the BindingResults:

public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { ... }

Complete example for form binding and validation:

/src/main/java/com/companyName/model/Todo.java:

package com.companyName.model;

import java.util.Date;

import javax.validation.constraints.Size;

public class Todo {
    private int id;

    private String user;

    @Size(min = 10, message = "Enter atleast 10 Characters.")
    private String desc;

    private Date targetDate;
    private boolean isDone;

    public Todo() {
        super();
    }

    public Todo(int id, String user, String desc, Date targetDate,
            boolean isDone) {
        super();
        this.id = id;
        this.user = user;
        this.desc = desc;
        this.targetDate = targetDate;
        this.isDone = isDone;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public Date getTargetDate() {
        return targetDate;
    }

    public void setTargetDate(Date targetDate) {
        this.targetDate = targetDate;
    }

    public boolean isDone() {
        return isDone;
    }

    public void setDone(boolean isDone) {
        this.isDone = isDone;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Todo other = (Todo) obj;
        if (id != other.id)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return String.format(
                "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id,
                user, desc, targetDate, isDone);
    }

}

/src/main/java/com/companyName/todo/service/TodoService.java:

package com.companyName.todo.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.springframework.stereotype.Service;

import com.companyName.model.Todo;

@Service
public class TodoService {
    private static List<Todo> todos = new ArrayList<Todo>();
    private static int todoCount = 3;

    static {
        todos.add(new Todo(1, "username", "Learn Spring MVC", new Date(),
                false));
        todos.add(new Todo(2, "username", "Learn Struts", new Date(), false));
        todos.add(new Todo(3, "username", "Learn Hibernate", new Date(),
                false));
    }

    public List<Todo> retrieveTodos(String user) {
        List<Todo> filteredTodos = new ArrayList<Todo>();
        for (Todo todo : todos) {
            if (todo.getUser().equals(user))
                filteredTodos.add(todo);
        }
        return filteredTodos;
    }

    public Todo retrieveTodo(int id) {
        for (Todo todo : todos) {
            if (todo.getId() == id)
                return todo;
        }
        return null;
    }

    public void updateTodo(Todo todo) {
        todos.remove(todo);
        todos.add(todo);
    }

    public void addTodo(String name, String desc, Date targetDate,
            boolean isDone) {
        todos.add(new Todo(++todoCount, name, desc, targetDate, isDone));
    }

    public void deleteTodo(int id) {
        Iterator<Todo> iterator = todos.iterator();
        while (iterator.hasNext()) {
            Todo todo = iterator.next();
            if (todo.getId() == id) {
                iterator.remove();
            }
        }
    }
}

/src/main/java/com/companyName/todo/TodoController.java:

package com.companyName.todo;

import java.util.Date;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;

import com.companyName.model.Todo;
import com.companyName.todo.service.TodoService;

@Controller
@SessionAttributes("name")
public class TodoController {

    @Autowired
    private TodoService service;

    @RequestMapping(value = "/list-todos", method = RequestMethod.GET)
    public String showTodosList(ModelMap model) {
        String user = (String) model.get("name");
        model.addAttribute("todos", service.retrieveTodos(user));
        return "list-todos";
    }

    @RequestMapping(value = "/add-todo", method = RequestMethod.GET)
    public String showAddTodoPage(ModelMap model) {
        model.addAttribute("todo", new Todo());
        return "todo";
    }

    @RequestMapping(value = "/add-todo", method = RequestMethod.POST)
    public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) {

        if (result.hasErrors())
            return "todo";

        service.addTodo((String) model.get("name"), todo.getDesc(), new Date(),
                false);
        model.clear();// to prevent request parameter "name" to be passed
        return "redirect:/list-todos";
    }

    @RequestMapping(value = "/update-todo", method = RequestMethod.GET)
    public String showUpdateTodoPage(ModelMap model, @RequestParam int id) {
        model.addAttribute("todo", service.retrieveTodo(id));
        return "todo";
    }

    @RequestMapping(value = "/update-todo", method = RequestMethod.POST)
    public String updateTodo(ModelMap model, @Valid Todo todo,
            BindingResult result) {
        if (result.hasErrors())
            return "todo";

        todo.setUser("username"); //TODO:Remove Hardcoding Later
        service.updateTodo(todo);

        model.clear();// to prevent request parameter "name" to be passed
        return "redirect:/list-todos";
    }

    @RequestMapping(value = "/delete-todo", method = RequestMethod.GET)
    public String deleteTodo(@RequestParam int id) {
        service.deleteTodo(id);

        return "redirect:/list-todos";
    }

}

/src/main/webapp/WEB-INF/views/list-todos.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Todos for ${name}</title>
<link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css"
    rel="stylesheet">
</head>
<body>
    <div class="container">
        <table class="table table-striped">
            <caption>Your Todos are</caption>
            <thead>
                <tr>
                    <th>Description</th>
                    <th>Date</th>
                    <th>Completed</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <c:forEach items="${todos}" var="todo">
                    <tr>
                        <td>${todo.desc}</td>
                        <td>${todo.targetDate}</td>
                        <td>${todo.done}</td>
                        <td>
                            <a type="button" class="btn btn-primary" 
                                href="/update-todo?id=${todo.id}">Edit</a>

                            <a type="button" class="btn btn-warning" 
                                href="/delete-todo?id=${todo.id}">Delete</a>
                        </td>
                    </tr>
                </c:forEach>
            </tbody>
        </table>
        <div>
            <a type="button" class="btn btn-success" href="/add-todo">Add</a>
        </div>
    </div>

    <script src="webjars/jquery/1.9.1/jquery.min.js"></script>
    <script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</body>
</html>

/src/main/webapp/WEB-INF/views/todo.jsp:

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
<title>Your Todo</title>
<link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css"
    rel="stylesheet">
</head>
<body>

    <div class="container">
        <form:form method="post" commandName="todo">
            <form:hidden path="id"/>
            <fieldset class="form-group">
                <form:label path="desc">Description</form:label>
                <form:input path="desc" type="text" class="form-control"
                    required="required"/>
                <form:errors path="desc" cssClass="text-warning" />
            </fieldset>
            <button type="submit" class="btn btn-success">Submit</button>
        </form:form>
    </div>

    <script src="webjars/jquery/1.9.1/jquery.min.js"></script>
    <script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>

</body>
</html>

/src/main/webapp/WEB-INF/web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>To do List</display-name>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/todo-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

How is validation done using Spring?

Validation can be done using the @Valid annotation. It support validation at both the web level and the business level.

public class CarValidator implements Validator {
  public boolean support(Class clazz) {
    return Car.class.equals(clazz);
  }
  public void validate(Object obj, Errors e) {
    ValidationUtils.rejectIfEmpty(e, "name", "name.is.empty");
    Car c = (Car) obj;
    if (c.getUsedYears() < 0) {
      e.rejectValue("usedYears", "not.yet.bought");
    }
  }
}

MessageCodesResolver can be used to convert the message code into proper internationalized message. See the form validation page as well.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License