
Spring Boot provides an easy way to manipulate the form data. Let's see how we can bind, validate, and send form data.
List of Contents
Using Fields
We can send the data in the template as shown below (name is the key)
<form th:action="@{/formSubmit} method="GET">
<input type="text" name="userName" />
</form>
There are two ways to get the sent data
Using getParameter
@RequestMapping("/resultForm")
public String processForm(HttpServletRequest request, Model model) {
String name = request.getParameter("userName")
model.addAttribute("name", name);
return "formResult";
}
Using @RequestParam
@RequestMapping("/resultForm")
public String processForm(@RequestParam("userName") String name, Model model) {
model.addAttribute("name", name);
return "formResult";
}
Using Form Object
This uses the setter when a form is submitted and the getter when the form is received to set and get the data
Entity Class
Create an entity class. Add no parameter constsructor, getter, and setter

Adding Object and Field in the Controller, Linking to a Template
@GetMapping("/userForm")
public String displayForm(Model model) {
User user = new User();
model.addAttribute("user", user);
return "user-form";
}
Creating a Form
Add a form in the template file and bind the object
<form th:action="@{/showResult}" th:object="${user}" method="POST">
</form>
Adding Form Fields
▶ Text
<input type="text" th:field="*{firstName}">
▶ Select
<select th:field="*{gender}">-->
<option th:value="Male">Male</option>-->
<option th:value="Female">Female</option>-->
</select>
▶ Radio
<input type="radio" th:field="*{level}" th:value="high">
<input type="radio" th:field="*{level}" th:value="low">
▶ Checkbox
<input type="checkbox" th:field="*{hobbies}" th:value="nap"/>
<input type="checkbox" th:field="*{hobbies}" th:value="excercise"/>
<input type="checkbox" th:field="*{hobbies}" th:value="'going home'"/>
For the fields that use multiple values such as the select or radio, we can use dynamic values with environment variables using the application.properties file

Add the environment variables as a property to the controller
@Value("${genders}")
private List<String> genders;
@Value("${levels}")
private List<String> levels;
@Value("${hobbies}")
private List<String> hobbies;
@GetMapping("/userForm")
public String displayForm(Model model) {
User user = new User();
model.addAttribute("user", user);
model.addAttribute("genders", genders);
model.addAttribute("levels", levels);
model.addAttribute("hobbies", hobbies);
return "user-form";
}
▶ Select (Using env variables)
<select th:field="*{gender}">
<option th:each="gender:${genders}" th:value="${gender}" th:text="${gender}"></option>
</select>
▶ Radio (Using env variables)
<input type="radio" th:field="*{level}" th:each="level:${levels}" th:value="${level}" th:text="${level}">
▶ Checkbox (Using env variables)
<input type="checkbox" th:field="*{hobbies}" th:each="hobby:${hobbies}" th:value="${hobby}" th:text="${hobby}"/>
Sending Data
▶ Controller
@PostMapping("/showResult")
public String showResult(@ModelAttribute("user") User user, BindingResult bindingResult, Model model) {
return "user-confirmation";
}
▶ Form Result
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>Result</h3>
<p th:text="${user.firstName}">
<p th:text="${user.gender}">
<p th:text="${user.level}">
<!--<p th:text="${user.hobbies}">-->
<ul>
<li th:each="hobby:${user.hobbies}" th:text="${hobby}"/>
</ul>
</body>
</html>
Completed Codes
▶ Entity
package com.example.form.entity;
import java.util.List;
public class User {
private String firstName;
private String lastName;
private String gender;
private String level;
private List<String> hobbies;
public User() {
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
▶ Controller
package com.example.form.controller;
import com.example.form.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Controller
public class UserController {
@Value("${genders}")
private List<String> genders;
@Value("${levels}")
private List<String> levels;
@Value("${hobbies}")
private List<String> hobbies;
@GetMapping("/userForm")
public String displayForm(Model model) {
User user = new User();
model.addAttribute("user", user);
model.addAttribute("genders", genders);
model.addAttribute("levels", levels);
model.addAttribute("hobbies", hobbies);
return "user-form";
}
@PostMapping("/showResult")
public String showResult(@ModelAttribute("user") User user, BindingResult bindingResult, Model model) {
System.out.println("bindingResult = " + bindingResult.toString());
return "user-confirmation";
}
}
▶ Sending Form
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>Form</h3>
<form th:action="@{/showResult}" th:object="${user}" method="POST">
<input type="text" th:field="*{firstName}">
<input type="text" th:field="*{lastName}">
<!-- <select th:field="*{gender}">-->
<!-- <option th:value="Male">Male</option>-->
<!-- <option th:value="Female">Female</option>-->
<!-- </select>-->
<select th:field="*{gender}">
<option th:each="gender:${genders}" th:value="${gender}" th:text="${gender}"></option>
</select>
<!-- <input type="radio" th:field="*{level}" th:value="high">-->
<!-- <input type="radio" th:field="*{level}" th:value="low">-->
<input type="radio" th:field="*{level}" th:each="level:${levels}" th:value="${level}" th:text="${level}">
<!-- <input type="checkbox" th:field="*{hobbies}" th:value="nap"/>-->
<!-- <input type="checkbox" th:field="*{hobbies}" th:value="excercise"/>-->
<!-- <input type="checkbox" th:field="*{hobbies}" th:value="'going home'"/>-->
<input type="checkbox" th:field="*{hobbies}" th:each="hobby:${hobbies}" th:value="${hobby}" th:text="${hobby}"/>
<input type="submit" value="Submit">
</form>
</body>
</html>
▶ Form Result
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>Result</h3>
<p th:text="${user.firstName}">
<p th:text="${user.gender}">
<p th:text="${user.level}">
<!--<p th:text="${user.hobbies}">-->
<ul>
<li th:each="hobby:${user.hobbies}" th:text="${hobby}"/>
</ul>
</body>
</html>
Validations
Dependencies
Add the dependency for the validation to the pom.xml file
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Adding Validation in Entity
Add validations to the entity class
▶ Mandatory
@NotNull(message = "required")
private String lastName;
▶ Size
@Size(min = 1, max = 3, message = "Must be more than 1 and less than 3")
private String lastName;
▶ Min
@Min(value = 0, message = "over 1")
private int level;
▶ Max
@Max(value = 10, message = "less than 10")
private int level;
▶ Regex
@Pattern(regexp = "$[a-zA-Z0-9]{5}", message = "format")
private String postal;
Showing Error Messages
We can show the error messages using the code shown below
th:if="${#fields.hasErrors('<fieldName>')}" th:errors="*{fieldName}"
th:if="${#fields.hasErrors('<fieldName>')}" th:errors="${user.fieldName}"
<form th:action="@{/resultForm}" th:object="${user}" method="POST">
<input type="text" th:field="*{firstName}"/>
<input type="text" th:field="*{lastName}"/>
<span th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"></span>
<input type="text" th:field="*{level}">
<span th:if="${#fields.hasErrors('level')}" th:errors="*{level}"></span>
<input type="text" th:field="*{postal}">
<span th:if="${#fields.hasErrors('postal')}" th:errors="*{postal}"></span>
<input type="text" th:field="*{code}">
<span th:if="${#fields.hasErrors('code')}" th:errors="*{code}"></span>
<input type="submit" value="Submit"/>
</form>
Custom Validations
Create a package then add an annotation below that

In the annotation created, add the annotations shown below. Note that we have to add the validation class used in the @Constraint
package com.example.formval.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = CodeValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Code {
public String value() default "CD";
public String message() default "start with CD";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
Add the validation class and inherit from the below interface
ConstraintValidator<<Annotation>, <Type>>

For example, to check if a value starts with a certain value, we can write a code as follows. The first method sets the entered value and the second method validates and returns a boolean
package com.example.formval.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CodeValidator implements ConstraintValidator<Code, String> {
private String prefix;
@Override
public void initialize(Code code) {
prefix = code.value();
}
@Override
public boolean isValid(String code, ConstraintValidatorContext constraintValidatorContext) {
boolean result = true;
if (code != null) {
result = code.startsWith(prefix);
}
return result;
}
}
▶ Using the Default Value
@Code
private String code;
▶ Overriding the Default Value
@Code(value = "CDO")
private String code;
We can see the errors thrown using the BindingResult
@PostMapping("/resultForm")
public String resultForm(@Valid @ModelAttribute("user") User user,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.toString());
return "user-form";
} else {
return "confirmation";
}
}
And using the property names in the codes array (The further right the less specific)

We can change the error messages in the messages.properties
typeMismatch.user.level=Invalid Number
Completed Codes
▶ Validation Annotation
package com.example.formval.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = CodeValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Code {
public String value() default "CD";
public String message() default "start with CD";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
▶ Validator Class
package com.example.formval.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CodeValidator implements ConstraintValidator<Code, String> {
private String prefix;
@Override
public void initialize(Code code) {
prefix = code.value();
}
@Override
public boolean isValid(String code, ConstraintValidatorContext constraintValidatorContext) {
boolean result = true;
if (code != null) {
result = code.startsWith(prefix);
}
return result;
}
}
▶ Entity
package com.example.formval.entity;
import com.example.formval.validation.Code;
import jakarta.validation.constraints.*;
public class User {
private String firstName;
@NotNull(message = "required")
@Size(min = 1, max = 3, message = "Must be more than 1 and less than 3")
private String lastName;
@Min(value = 0, message = "over 1")
@Max(value = 10, message = "less than 10")
private int level;
@NotNull
private String levelMen;
@Pattern(regexp = "$[a-zA-Z0-9]{5}", message = "format")
private String postal;
@Code(value = "CDO")
private String code;
public User() {
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLevelMen() {
return levelMen;
}
public void setLevelMen(String levelMen) {
this.levelMen = levelMen;
}
public String getPostal() {
return postal;
}
public void setPostal(String postal) {
this.postal = postal;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
▶ Controller
package com.example.formval.controller;
import com.example.formval.entity.User;
import jakarta.validation.Valid;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class UserController {
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
@GetMapping("/")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "user-form";
}
@PostMapping("/resultForm")
public String resultForm(
@Valid @ModelAttribute("user") User user,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.toString());
return "user-form";
} else {
return "confirmation";
}
}
}
▶ Sending Form
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form th:action="@{/resultForm}" th:object="${user}" method="POST">
<input type="text" th:field="*{firstName}"/>
<input type="text" th:field="*{lastName}"/>
<span th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"></span>
<input type="text" th:field="*{level}">
<span th:if="${#fields.hasErrors('level')}" th:errors="*{level}"></span>
<input type="text" th:field="*{postal}">
<span th:if="${#fields.hasErrors('postal')}" th:errors="*{postal}"></span>
<input type="text" th:field="*{code}">
<span th:if="${#fields.hasErrors('code')}" th:errors="*{code}"></span>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
▶ Form Result
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${user.firstName + ' ' + user.lastName + ' ' + user.level + ' ' + user.postal + ' ' + user.code"/>
</body>
</html>
In this writing, we have seen Spring Boot form and how we can use it.
References
Jakarta Bean Validation - Home
Jakarta Bean Validation - Home
Jakarta Bean Validation lets you express constraints on object models via annotations lets you write custom constraints in an extensible way provides the APIs to validate objects and object graphs provide the APIs to validate
beanvalidation.org
'Backend > Java' 카테고리의 다른 글
Class (0) | 2023.12.28 |
---|---|
Switch (0) | 2023.12.26 |
Spring Beans - Dependency Injection (1) | 2023.10.07 |
Spring Boot Security (1) | 2023.10.01 |
Spring Boot Actuator (0) | 2023.10.01 |