Bean Validation

Bean Validation checks user input against rules written as annotations on the Java fields, such as @NotBlank, @Email and @Min.It is used so bad data (empty names, wrong emails, invalid mobile numbers) is rejected before it reaches your logic or the database.You put the rules on the model, add @Valid on the controller parameter, and Spring runs the checks automatically on every request.If a rule fails, the request is rejected with a message you wrote. In a form the error shows beside the field, and in a JSON API it comes back as a 400 response.

Features

  • Rules sit on the fields, in one place, instead of if checks scattered in the code.
  • Each rule has its own custom message.
  • The same rules work for the HTML form (BindingResult) and the JSON API (@RequestBody).
  • All errors are reported at once, not one at a time.
  • Invalid data is never saved to MySQL.
  • Needs the spring-boot-starter-validation dependency.

Hands-on Experiment: Creating a spring boot application with Validations

The following steps are involved in developing this hands-on experiment.

Step 1: Generate the Project

Step 2: Project Structure

Step 3: pom.xml

Step 4: application.properties

Step 5: Create Entity Class

Step 6: Create Repository Interface

Step 7: Create Service Class

Step 8: Create Controller Class

Step 9: Main Application Class

Step 10: Run the Main Application Class

Step 1: Configure the Project on Spring Initializr

Go to start.spring.io and set:

  • Project: Maven
  • Language: Java
  • Spring Boot version: 3.2.x
  • Group: com.example
  • Artifact: validation
  • Name: validation
  • Package name: com.example.validation
  • Packaging: Jar
  • Java: 17
  • Dependencies: Spring Web, Spring Data JPA, MySQL Driver, Thymeleaf, Validation I/O.

Click Generate to download the ZIP, then extract and import it into your IDE as a Maven project.

Step 2: Generated Project Structure

Step 3: Explore pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>student-validation-demo</artifactId>
    <version>1.0.0</version>
    <name>student-validation-demo</name>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Step 4: application.properties

spring.application.name=student-validation-demo
server.port=8082

spring.datasource.url=jdbc:mysql://localhost:3306/student_validation_db?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

spring.thymeleaf.cache=falseCode language: JavaScript (javascript)

Step 5: Create Entity Class

package com.example.validation.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // Required, and 3 to 50 characters long
    @NotBlank(message = "Name is required")
    @Size(min = 3, max = 50, message = "Name must be between 3 and 50 characters")
    private String name;

    // Required, and must look like an email address
    @NotBlank(message = "Email is required")
    @Email(message = "Enter a valid email address")
    private String email;

    // Required, and between 18 and 60
    @NotNull(message = "Age is required")
    @Min(value = 18, message = "Age must be at least 18")
    @Max(value = 60, message = "Age must not be more than 60")
    private Integer age;

    // Required, and exactly 10 digits starting with 6, 7, 8 or 9
    @NotBlank(message = "Mobile number is required")
    @Pattern(regexp = "^[6-9][0-9]{9}$",
            message = "Mobile number must be 10 digits and start with 6, 7, 8 or 9")
    @Column(length = 10)
    private String mobile;

    // Required, and must be one of the offered courses
    @NotBlank(message = "Course is required")
    @Pattern(regexp = "Java|Spring Boot|SQL|React|Python",
            message = "Course must be one of: Java, Spring Boot, SQL, React, Python")
    private String course;

    public Student() {
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }

    public String getMobile() { return mobile; }
    public void setMobile(String mobile) { this.mobile = mobile; }

    public String getCourse() { return course; }
    public void setCourse(String course) { this.course = course; }
}

Step 6: Create below Repository Interface

package com.example.validation.repository;

import com.example.validation.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}

Step 7: Create below Service class

package com.example.validation.service;

import com.example.validation.entity.Student;
import com.example.validation.repository.StudentRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentService {

    private final StudentRepository studentRepository;

    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public Student register(Student student) {
        student.setId(null);   // a client must never choose the database id
        return studentRepository.save(student);
    }

    public List<Student> getAll() {
        return studentRepository.findAll();
    }
}

Step 8: Create Controller class

package com.example.validation.controller;

import com.example.validation.entity.Student;
import com.example.validation.service.StudentService;
import jakarta.validation.Valid;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
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;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("/students")
public class StudentController {

    private static final List<String> COURSES = List.of("Java", "Spring Boot", "SQL", "React", "Python");

    private final StudentService studentService;

    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    // Trim spaces and turn empty form text into null, so "required" messages show once
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setDisallowedFields("id");
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
    }

    // ---------- HTML form ----------

    // GET /students/register
    @GetMapping("/register")
    public String showForm(Model model) {
        model.addAttribute("student", new Student());
        model.addAttribute("courseOptions", COURSES);
        return "register";
    }

    // POST /students/register  (@Valid runs the rules; errors land in BindingResult)
    @PostMapping("/register")
    public String register(@Valid @ModelAttribute("student") Student student,
                           BindingResult result,
                           Model model,
                           RedirectAttributes redirectAttributes) {
        if (result.hasErrors()) {
            model.addAttribute("courseOptions", COURSES);
            return "register";   // show the form again with the error messages
        }
        Student saved = studentService.register(student);
        redirectAttributes.addFlashAttribute("message",
                "Student '" + saved.getName() + "' registered successfully with id " + saved.getId());
        return "redirect:/students";
    }

    // GET /students
    @GetMapping
    public String list(Model model) {
        model.addAttribute("students", studentService.getAll());
        return "students";
    }

    // ---------- JSON API (for Postman) ----------

    // POST /students/api  (@Valid on @RequestBody; failures throw MethodArgumentNotValidException)
    @PostMapping("/api")
    @ResponseBody
    public ResponseEntity<Student> createJson(@Valid @RequestBody Student student) {
        return ResponseEntity.status(HttpStatus.CREATED).body(studentService.register(student));
    }

    // GET /students/json
    @GetMapping("/json")
    @ResponseBody
    public List<Student> listJson() {
        return studentService.getAll();
    }

    // Turns validation failures of the JSON API into a clean 400 response: { field: message }
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors()
                .forEach(error -> errors.merge(error.getField(), error.getDefaultMessage(),
                        (first, second) -> first + "; " + second));
        return ResponseEntity.badRequest().body(errors);
    }
}

Step 9: Main Application

package com.example.validation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentValidationApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudentValidationApplication.class, args);
    }
}

Step 10: Run the StudentVAlidationApplication

Scroll to Top