nirmalakumarsahu

Spring Boot Validation

πŸ“„ Articles πŸ‘€ My Profile

Spring Boot Validation

Spring Framework Framework


πŸ“‘ Index


βœ… What is Validation in Spring Boot?

Validation is the process of ensuring that the data provided by a user (via an API, form, or request) is correct, complete, and within the required rules before processing it further.

✨ For example:

Spring Boot provides powerful validation support using Jakarta Bean Validation (JSR 380) (formerly Hibernate Validator).

πŸ” Back to Top


🏷️ Different Validation Annotations in Spring Boot

Here are the most commonly used annotations you’ll encounter πŸ‘‡

1. Null / Empty Checks

πŸ“Œ Example:

public class UserRequest {
    @NotNull(message = "ID cannot be null")
    private Long id;

    @NotEmpty(message = "Username cannot be empty")
    private String username;

    @NotBlank(message = "Password cannot be blank")
    private String password;
}

2. String Length / Size

πŸ“Œ Example:

@Size(min = 3, max = 20, message = "Username must be 3-20 chars")
private String username;

3. Number Validations

πŸ“Œ Example:

@Min(value = 18, message = "Age must be at least 18")
@Max(value = 60, message = "Age must be at most 60")
private int age;

@Digits(integer = 6, fraction = 2, message = "Amount format is invalid")
private BigDecimal amount;

4. Boolean Checks

πŸ“Œ Example:

@AssertTrue(message = "Must accept terms & conditions")
private boolean accepted;

5. Date & Time Constraints

πŸ“Œ Example:

@Past(message = "DOB must be in the past")
private LocalDate dob;

@Future(message = "Booking date must be in the future")
private LocalDate bookingDate;

6. String Format Validators

πŸ“Œ Example:

@Email(message = "Invalid email")
private String email;

@Pattern(regexp = "^[0-9]{10}$", message = "Phone must be 10 digits")
private String phone;

7. Custom Validation ✨

When built-in ones are not enough, you can define your own rules.

πŸ“Œ Example:

@StrongPassword
private String password;

(Here, @StrongPassword is a custom annotation validated via ConstraintValidator.)

πŸ“Š Quick Reference Table

🏷️ Annotation πŸ“– Meaning
🚫 @Null Must be null
βœ… @NotNull Must not be null
🧾 @NotBlank Must not be blank
✍️ @NotEmpty Not empty
πŸ“ @Size Length/size restriction
πŸ”’ @Min / @Max Number range
βž• @Positive Positive value
βž– @Negative Negative value
πŸ’― @Digits Number format
βœ… @AssertTrue Must be true
❌ @AssertFalse Must be false
⏳ @Past Past date
πŸ“… @Future Future date
πŸ“§ @Email Valid email
πŸ”€ @Pattern Regex check

πŸ‘‰ Best Practice (Industry Standard):

πŸ” Back to Top


βš™οΈ How to Implement Validation in Spring Boot

There are multiple ways you can implement validation. Let’s go step by step.

1️⃣ Using the Sping boot Starter Validation dependency,

If using Maven, add the following to your pom.xml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

or if you are using Gradle, add the following to your build.gradle:

implementation 'org.springframework.boot:spring-boot-starter-validation'

2️⃣ Using Bean Validation Annotations (Most Common)

You annotate your DTOs (Request classes) with validation constraints.

Example:

import jakarta.validation.constraints.*;

public class UserRequest {

    @NotBlank(message = "Name cannot be blank")
    private String name;

    @Email(message = "Invalid email format")
    private String email;

    @Min(value = 18, message = "Age must be at least 18")
    private int age;

    // Getters and Setters
}

Controller

import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import jakarta.validation.Valid;

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody UserRequest userRequest) {
        return ResponseEntity.ok("User Created Successfully!");
    }
}

πŸ‘‰ Here, @Valid ensures validation is applied. If validation fails, Spring throws MethodArgumentNotValidException.

3️⃣ Custom Error Handling with @ControllerAdvice or @RestControllerAdvice

To make responses user-friendly:

import org.springframework.http.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}

πŸ‘‰ Now invalid input returns structured JSON errors.

4️⃣ Method-Level Validation

Spring also supports validating method parameters & return values with @Validated.

import jakarta.validation.constraints.Min;
import org.springframework.validation.annotation.Validated;
import org.springframework.stereotype.Service;

@Service
@Validated
public class PaymentService {

    public void processPayment(@Min(1) double amount) {
        System.out.println("Processing payment: " + amount);
    }
}

πŸ‘‰ If someone calls processPayment(0), Spring throws a validation error.

πŸ” Back to Top


🎯 Custom Validation in Spring Boot

When built-in annotations like @NotBlank or @Email aren’t enough, you can create your own validation annotation + custom validator class.

1️⃣ Create a Custom Validation Annotation

πŸ“Œ The annotation defines:

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

@Constraint(validatedBy = StrongPasswordValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface StrongPassword {
    String message() default "Password must contain at least 1 uppercase, 1 lowercase, 1 digit, and 1 special character";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2️⃣ Create the Custom Validator Class

πŸ“Œ This class must implement ConstraintValidator<AnnotationType, FieldType>.

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {

    @Override
    public void initialize(CustomValidation constraintAnnotation) {
        // (Optional) Initialize validator if needed
    }
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&]).+$");
    }
}

3️⃣ Apply Custom Validation in DTO/Entity

πŸ“Œ Just use the annotation like a built-in validator.

public class RegisterRequest {
    @StrongPassword
    private String password;
}

⚑ Real-World Use Cases for Custom Validators

βœ… Password strength β†’ @StrongPassword

βœ… Confirm password match β†’ @PasswordMatches

βœ… Unique email (DB check) β†’ @UniqueEmail

βœ… PAN/Aadhar/SSN validation β†’ @ValidPAN

βœ… Mobile number format β†’ @ValidPhone

🏷️ Annotation Elements in Spring Validation

When you create a custom validation annotation, you usually see these three properties:

public @interface StrongPassword {
    String message() default "Password must contain at least 1 uppercase, 1 lowercase, 1 digit, and 1 special character";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Let’s break them down one by one πŸ‘‡

1️⃣ String message() default "...";

βœ… What is it?

βœ… Why do we need it?

βœ… How to use it?

@StrongPassword(message = "Password must follow company policy")
private String password;

Here, instead of the default, it will show β€œPassword must follow company policy” when validation fails.

2️⃣ Class<?>[] groups() default {};

βœ… What is it?

βœ… Why do we need it?

βœ… How to use it?

public interface CreateGroup {}
public interface UpdateGroup {}

public class UserRequest {
    @NotNull(groups = CreateGroup.class)
    private String name;

    @NotNull(groups = {CreateGroup.class, UpdateGroup.class})
    private String email;
}

Then, in your controller/service:

@PostMapping("/create")
public ResponseEntity<?> create(@Validated(CreateGroup.class) @RequestBody UserRequest user) {
    ...
}

Here only validations in the CreateGroup will run.

3️⃣ Class<? extends Payload>[] payload() default {};

βœ… What is it?

βœ… Why do we need it?

βœ… How to use it? First, define your own Payload:

public class Severity {
    public interface Info extends Payload {}
    public interface Critical extends Payload {}
}

Then apply:

@StrongPassword(payload = {Severity.Critical.class})
private String password;

Now your validator (or custom error handler) can check the payload type and decide how to handle the error (e.g., log it differently or give different HTTP status).

βœ… Summary

Attribute Purpose Typical Usage
message() Default error message @NotNull(message = "Name is required")
groups() Conditional / grouped validations @Validated(CreateGroup.class)
payload() Extra metadata (severity, category, etc.) Advanced / custom cases

πŸ‘‰ So, you’ll almost always use message(), sometimes groups(), and rarely payload() unless you’re building a very advanced system.

πŸ” Back to Top


βœ… Validation Groups in Spring Boot

Validation groups let you apply different validation rules for different use cases on the same entity/model.

For example:

1️⃣ Create Marker Interfaces

Marker interfaces represent validation groups.

package com.sahu.springboot.validation.group;

public interface BasicInfo {}
public interface AdvancedInfo {}

2️⃣ Entity with Group-based Validation

Apply different constraints for different groups.

package com.sahu.springboot.validation.model;

import com.sahu.springboot.validation.group.BasicInfo;
import com.sahu.springboot.validation.group.AdvancedInfo;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class User {

    @NotNull(message = "Username is required", groups = {BasicInfo.class, AdvancedInfo.class})
    private String username;

    @NotNull(message = "Email is required for advanced info", groups = AdvancedInfo.class)
    private String email;

}

3️⃣ Controller Using @Validated

We can validate the same User object differently depending on the endpoint.

package com.sahu.springboot.validation.controller;

import com.sahu.springboot.validation.group.BasicInfo;
import com.sahu.springboot.validation.group.AdvancedInfo;
import com.sahu.springboot.validation.model.User;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    //For Form based applications
    @PostMapping("/registerBasic")
    public String registerBasicInfo(@Validated(BasicInfo.class) @RequestBody User user,
                                    BindingResult result) {
        if (result.hasErrors()) {
            return "❌ Basic Info Validation Failed: " + result.getAllErrors();
        }
        return "βœ… Basic Info Registered Successfully!";
    }
    
    @PostMapping("/registerAdvanced")
    public String registerAdvancedInfo(@Validated(AdvancedInfo.class) @RequestBody User user) {
        return "βœ… Advanced Info Registered Successfully!";
    }
}

πŸ”‘ Key Points

  1. Marker Interfaces β†’ represent validation groups.
  2. @Validated(Group.class) β†’ tells Spring which group to validate.
  3. Same entity (User) can have different rules per use case.

πŸ” Back to Top


πŸš€ Implementation

πŸ—οΈ Technology Stack

πŸ–₯️ Backend

πŸ“Š API Documentation

πŸ›’οΈ Database

πŸ› οΈ Build & Dependency Management

βš™οΈ Utilities

πŸ“‚ Project Structure

spring-boot-validation
│── πŸ“‚ src/
β”‚   └── πŸ“‚ main/
β”‚       β”œβ”€β”€ πŸ“‚ java/
β”‚       β”‚   └── πŸ“‚ com/sahu/springboot/basics/
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ config/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ OpenApiConfig.java
β”‚       β”‚       β”‚   └── πŸ“„ OpenApiProperties.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ constant/
β”‚       β”‚       β”‚   └── πŸ“„ ApiStatus.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ controller/rest/
β”‚       β”‚       β”‚   └── πŸ“‚ rest/
β”‚       β”‚       β”‚       β”œβ”€β”€ πŸ“„ ProductRestController.java
β”‚       β”‚       β”‚       └── πŸ“„ UserRestController.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ dto/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ApiResponse.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductRequest.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductResponse.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ UserRequest.java
β”‚       β”‚       β”‚   └── πŸ“„ UserResponse.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ exception/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ GlobalExceptionHandler.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductAlreadyExistException.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductNotFoundException.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ UserAlreadyExistException.java
β”‚       β”‚       β”‚   └── πŸ“„ UserNotFoundException.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ model/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ Product.java
β”‚       β”‚       β”‚   └── πŸ“„ User.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ repository/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductRepository.java
β”‚       β”‚       β”‚   └── πŸ“„ UserRepository.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ service/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“‚ impl/
β”‚       β”‚       β”‚   β”‚   β”œβ”€β”€ πŸ“„ ProductServiceImpl.java
β”‚       β”‚       β”‚   β”‚   └── πŸ“„ UserServiceImpl.java
β”‚       β”‚       β”‚   β”‚
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“‚ util/
β”‚       β”‚       β”‚   β”‚   β”œβ”€β”€ πŸ“„ ProductUtil.java
β”‚       β”‚       β”‚   β”‚   └── πŸ“„ UserUtil.java
β”‚       β”‚       β”‚   β”‚
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductService.java
β”‚       β”‚       β”‚   └── πŸ“„ UserService.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ validation/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“‚ group/
β”‚       β”‚       β”‚   β”‚   β”œβ”€β”€ πŸ“„ CreateGroup.java
β”‚       β”‚       β”‚   β”‚   └── πŸ“„ UpdateGroup.java
β”‚       β”‚       β”‚   β”‚
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ StrongPassword.java
β”‚       β”‚       β”‚   └── πŸ“„ StrongPasswordValidator.java
β”‚       β”‚       β”‚
β”‚       β”‚       └── πŸ“„ SpringBootValidationApplication.java
β”‚       β”‚
β”‚       └── πŸ“‚ resources/
β”‚           └── πŸ“„ application.yml
β”‚
│── πŸ“„ docker-compose.yml
└── πŸ“„ pom.xml

πŸ”— Code Repository

You can find the complete code repository for this project on GitHub:

GitHub - spring-boot-validation

πŸš€ To Run the Spring Boot Application

1️⃣ 🐳 Using Docker Compose (for MySQL container)

docker-compose up -d

βœ… This starts MySQL in a container (-d = detached mode). πŸ” Verify with:

docker ps

πŸ“Œ DB is now available at localhost:3307. πŸ”‘ Credentials (username, password, DB name) are in docker-compose.yml.

2️⃣ πŸ’» Run Directly in IntelliJ IDEA

  1. πŸ“‚ Open the Spring Boot project in IntelliJ.
  2. ▢️ In Project Explorer, right-click the main class: SpringBootValidationApplication.java
  3. Select Run β€˜SpringBootValidationApplication’.
  4. 🐞 For debugging, click the Debug button instead of Run.
  5. 🌐 App will start on http://localhost:9897.

3️⃣ ⚑ Run with Maven Command (CLI)

πŸ” Back to Top


πŸŽ₯ Video Reference

For a detailed running and demonstration of the application walkthrough,
watch the following YouTube video:

Coming Soon…

πŸ” Back to Top

πŸ“– Read More ➑️