Validation

01.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 22 daqiqa o'qish

Validation - foydalanuvchi yoki frontend yuborgan ma’lumotni tekshirish. Bu bo‘lim ichida quyidagilar bor: Bean Validation, @Valid, @Validated, @NotNull, @NotBlank, @Size, @Email, @Min, @Max, custom constraint annotations va validation groups.


1. Validation nima?

Masalan frontend sizga user yaratish uchun request yuboradi:

{
  "fullName": "",
  "email": "wrong-email",
  "age": -5
}

Bu data noto‘g‘ri:

fullName bo‘sh
email formati xato
age manfiy

Shu narsalarni databasega saqlab yubormaslik kerak. Mana shu tekshirish jarayoni validation deyiladi.


2. Validation qayerda ishlatiladi?

Spring Boot’da odatda validation DTO classlarda yoziladi.

Masalan:

public record UserCreateRequest(
        String fullName,
        String email,
        Integer age
) {
}

Buni validation bilan kuchaytiramiz:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record UserCreateRequest(

        @NotBlank(message = "Full name bo‘sh bo‘lmasligi kerak")
        String fullName,

        @NotBlank(message = "Email bo‘sh bo‘lmasligi kerak")
        @Email(message = "Email formati noto‘g‘ri")
        String email,

        @NotNull(message = "Age majburiy")
        @Min(value = 18, message = "Yosh kamida 18 bo‘lishi kerak")
        Integer age
) {
}

Endi noto‘g‘ri request kelsa, Spring uni avtomatik ushlab qoladi.


3. Kerakli dependency

Spring Boot 3'da validation ishlatish uchun odatda shu dependency kerak bo‘ladi:

Maven

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

Gradle

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

Agar bu dependency bo‘lmasa, @Valid, @NotBlank, @Email ishlamaydi yoki kutilganidek validation qilmaydi.


4. @Valid nima qiladi?

DTO ichidagi validation annotationlarni ishga tushirish uchun controller’da @Valid yoziladi.

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

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public UserResponse create(@Valid @RequestBody UserCreateRequest request) {
        return userService.create(request);
    }
}

Bu yerda:

@Valid @RequestBody UserCreateRequest request

ma’nosi:

Request body’dan UserCreateRequest yasagin,
keyin uning validation qoidalarini tekshirgin.

Agar validation xato bo‘lsa, method ichiga kirmaydi.

Ya’ni bu kod chaqirilmaydi:

return userService.create(request);

5. Eng ko‘p ishlatiladigan annotationlar

@NotNull

Qiymat null bo‘lmasligi kerak.

@NotNull(message = "Age majburiy")
private Integer age;

Bu holat xato:

{
  "age": null
}

Lekin bu xato emas:

{
  "age": 0
}

@NotNull faqat nullni tekshiradi.


@NotBlank

String bo‘sh yoki faqat probel bo‘lmasligi kerak.

@NotBlank(message = "Name bo‘sh bo‘lmasligi kerak")
private String name;

Quyidagilar xato:

{ "name": "" }
{ "name": "   " }
{ "name": null }

String uchun ko‘pincha @NotBlank ishlatiladi.


@NotEmpty

Collection, array yoki string bo‘sh bo‘lmasligi kerak.

@NotEmpty(message = "Taglar bo‘sh bo‘lmasligi kerak")
private List<String> tags;

Farqi:

@NotNull  → null bo‘lmasin
@NotEmpty → null bo‘lmasin va size > 0 bo‘lsin
@NotBlank → null bo‘lmasin, "" bo‘lmasin, "   " bo‘lmasin

@Size

String yoki list uzunligini tekshiradi.

@Size(min = 3, max = 50, message = "Name 3 dan 50 tagacha belgi bo‘lishi kerak")
private String name;

Misol:

{ "name": "Al" }

Bu xato, chunki uzunligi 2.


@Email

Email formatini tekshiradi.

@Email(message = "Email formati noto‘g‘ri")
private String email;

Xato:

{ "email": "ali-mail.com" }

To‘g‘ri:

{ "email": "ali@mail.com" }

Amaliy maslahat: @Email bilan birga @NotBlank ham qo‘ying.

@NotBlank(message = "Email majburiy")
@Email(message = "Email formati noto‘g‘ri")
private String email;

Chunki @Email har doim ham bo‘sh qiymatni siz kutgandek ushlamasligi mumkin.


@Min va @Max

Sonning minimal va maksimal qiymatini tekshiradi.

@Min(value = 18, message = "Yosh kamida 18 bo‘lishi kerak")
@Max(value = 100, message = "Yosh 100 dan katta bo‘lmasligi kerak")
private Integer age;

@Positive va @PositiveOrZero

Musbat sonlarni tekshiradi.

@Positive(message = "Narx musbat bo‘lishi kerak")
private BigDecimal price;

Bu xato:

{ "price": 0 }

Chunki @Positive uchun son 0 dan katta bo‘lishi kerak.

Agar 0 ham mumkin bo‘lsa:

@PositiveOrZero(message = "Narx 0 yoki musbat bo‘lishi kerak")
private BigDecimal price;

@Past va @Future

Sana bilan ishlaganda kerak bo‘ladi.

@Past(message = "Tug‘ilgan sana o‘tmishda bo‘lishi kerak")
private LocalDate birthDate;
@Future(message = "Deadline kelajakda bo‘lishi kerak")
private LocalDate deadline;

6. Product request validation misoli

Oldingi Product CRUD API uchun request DTO:

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;

import java.math.BigDecimal;

public record ProductCreateRequest(

        @NotBlank(message = "Product name majburiy")
        @Size(min = 2, max = 100, message = "Product name 2 dan 100 tagacha bo‘lishi kerak")
        String name,

        @NotNull(message = "Price majburiy")
        @Positive(message = "Price 0 dan katta bo‘lishi kerak")
        BigDecimal price,

        @NotNull(message = "Quantity majburiy")
        @PositiveOrZero(message = "Quantity 0 yoki musbat bo‘lishi kerak")
        Integer quantity,

        @NotNull(message = "Category id majburiy")
        Long categoryId
) {
}

Controller:

@PostMapping
public ProductResponse create(@Valid @RequestBody ProductCreateRequest request) {
    return productService.create(request);
}

Xato request:

{
  "name": "",
  "price": -100,
  "quantity": -2,
  "categoryId": null
}

Bu request service’ga yetib bormaydi.


7. Update request alohida bo‘lishi kerak

Create va update uchun bitta DTO ishlatish har doim ham yaxshi emas.

Create’da hamma field majburiy bo‘lishi mumkin:

public record ProductCreateRequest(

        @NotBlank(message = "Name majburiy")
        String name,

        @NotNull(message = "Price majburiy")
        @Positive(message = "Price 0 dan katta bo‘lishi kerak")
        BigDecimal price,

        @NotNull(message = "Quantity majburiy")
        @PositiveOrZero(message = "Quantity 0 yoki musbat bo‘lishi kerak")
        Integer quantity
) {
}

Update’da esa ba’zi fieldlar optional bo‘lishi mumkin:

public record ProductUpdateRequest(

        @Size(min = 2, max = 100, message = "Name 2 dan 100 tagacha bo‘lishi kerak")
        String name,

        @Positive(message = "Price 0 dan katta bo‘lishi kerak")
        BigDecimal price,

        @PositiveOrZero(message = "Quantity 0 yoki musbat bo‘lishi kerak")
        Integer quantity
) {
}

Bu yerda @NotNull yo‘q, chunki update’da hammasini yuborish majburiy emas.


8. Validation xatolari qanday qaytadi?

Default holatda Spring Boot validation xatosini qaytaradi, lekin response ko‘pincha chiroyli bo‘lmaydi.

Shuning uchun @RestControllerAdvice bilan global error handler yoziladi.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidation(
            MethodArgumentNotValidException ex
    ) {
        Map<String, String> errors = new HashMap<>();

        ex.getBindingResult()
                .getFieldErrors()
                .forEach(error ->
                        errors.put(error.getField(), error.getDefaultMessage())
                );

        return ResponseEntity.badRequest().body(errors);
    }
}

Endi xato response shunday bo‘ladi:

{
  "name": "Product name majburiy",
  "price": "Price 0 dan katta bo‘lishi kerak",
  "quantity": "Quantity 0 yoki musbat bo‘lishi kerak",
  "categoryId": "Category id majburiy"
}

Bu frontend uchun ancha qulay.


9. Yaxshiroq error response format

Real loyihada hamma errorlar bir xil formatda bo‘lgani yaxshi.

Masalan:

public record ErrorResponse(
        String code,
        String message,
        Map<String, String> fields
) {
}

Handler:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex
    ) {
        Map<String, String> fields = new HashMap<>();

        ex.getBindingResult()
                .getFieldErrors()
                .forEach(error ->
                        fields.put(error.getField(), error.getDefaultMessage())
                );

        ErrorResponse response = new ErrorResponse(
                "VALIDATION_ERROR",
                "Request data noto‘g‘ri",
                fields
        );

        return ResponseEntity.badRequest().body(response);
    }
}

Response:

{
  "code": "VALIDATION_ERROR",
  "message": "Request data noto‘g‘ri",
  "fields": {
    "name": "Product name majburiy",
    "price": "Price 0 dan katta bo‘lishi kerak"
  }
}

10. @Valid nested objectlarda

Agar request ichida boshqa object bo‘lsa, ichki objectni ham tekshirish uchun @Valid kerak.

Masalan:

public record CreateOrderRequest(

        @NotNull(message = "Customer majburiy")
        @Valid
        CustomerRequest customer,

        @NotEmpty(message = "Order itemlar bo‘sh bo‘lmasligi kerak")
        List<@Valid OrderItemRequest> items
) {
}

Customer:

public record CustomerRequest(

        @NotBlank(message = "Customer name majburiy")
        String name,

        @NotBlank(message = "Phone majburiy")
        String phone
) {
}

Order item:

public record OrderItemRequest(

        @NotNull(message = "Product id majburiy")
        Long productId,

        @NotNull(message = "Quantity majburiy")
        @Positive(message = "Quantity 0 dan katta bo‘lishi kerak")
        Integer quantity
) {
}

Muhim joy:

List<@Valid OrderItemRequest> items

Bu list ichidagi har bir itemni ham validate qiladi.


11. PathVariable va RequestParam validation

Faqat @RequestBody emas, @PathVariable va @RequestParam ham validate qilinadi.

Buning uchun controller class ustiga @Validated qo‘yiladi.

@RestController
@RequestMapping("/api/products")
@Validated
public class ProductController {

    @GetMapping("/{id}")
    public ProductResponse getById(
            @PathVariable @Positive(message = "Id musbat bo‘lishi kerak") Long id
    ) {
        return productService.getById(id);
    }

    @GetMapping
    public Page<ProductResponse> getAll(
            @RequestParam(defaultValue = "0") @Min(0) int page,
            @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size
    ) {
        return productService.getAll(page, size);
    }
}

Request:

GET /api/products?page=-1&size=1000

Bu xato bo‘ladi.


12. @Valid va @Validated farqi

@Valid

Ko‘proq request body DTO’ni validate qilish uchun ishlatiladi:

public ProductResponse create(@Valid @RequestBody ProductCreateRequest request)

@Validated

Spring’ning annotationi. Ko‘proq:

PathVariable validation
RequestParam validation
Validation groups
Service method validation

uchun ishlatiladi.

Misol:

@Validated
@RestController
public class ProductController {
}

Oddiy qoida:

@RequestBody DTO uchun → @Valid
@PathVariable / @RequestParam uchun → class ustiga @Validated
Validation groups uchun → @Validated(Group.class)

13. Service method validation

Ba’zan validation service methodlarda ham kerak bo‘ladi.

@Service
@Validated
public class ProductService {

    public ProductResponse getById(
            @Positive(message = "Id musbat bo‘lishi kerak") Long id
    ) {
        // logic
    }
}

Lekin amaliyotda ko‘pincha request validation controller’da qilinadi. Service’da esa business validation bo‘ladi.


14. Input validation vs Business validation

Bularni ajratish juda muhim.

Input validation

Request formatini tekshiradi:

name bo‘sh emasmi?
email to‘g‘ri formatdami?
age 18 dan kattami?
price musbatmi?

Bular DTO’da annotation bilan yoziladi.

@NotBlank
@Email
@Min
@Positive

Business validation

Biznes qoida bo‘yicha tekshiradi:

Email database’da bor-yo‘qligi
Product stock yetarlimi
User blocked emasmi
Category mavjudmi
Order status CANCELLED bo‘lsa update qilish mumkin emasmi

Bular service layer’da yoziladi.

Masalan:

@Transactional
public ProductResponse create(ProductCreateRequest request) {
    if (productRepository.existsByName(request.name())) {
        throw new BadRequestException("Product name already exists");
    }

    Category category = categoryRepository.findById(request.categoryId())
            .orElseThrow(() -> new NotFoundException("Category not found"));

    Product product = productMapper.toEntity(request, category);

    return productMapper.toResponse(productRepository.save(product));
}

categoryId null emasligini DTO tekshiradi. Lekin category database’da bormi - buni service tekshiradi.


15. Custom exception bilan ishlash

Service’da IllegalArgumentException ishlatish mumkin, lekin real loyihada custom exception yaxshiroq.

public class BadRequestException extends RuntimeException {

    public BadRequestException(String message) {
        super(message);
    }
}
public class NotFoundException extends RuntimeException {

    public NotFoundException(String message) {
        super(message);
    }
}

Global handler:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleBadRequest(BadRequestException ex) {
        return ResponseEntity.badRequest().body(
                new ErrorResponse("BAD_REQUEST", ex.getMessage(), null)
        );
    }

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(NotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
                new ErrorResponse("NOT_FOUND", ex.getMessage(), null)
        );
    }
}

16. Custom validation annotation

Ba’zan tayyor annotation yetmaydi.

Masalan telefon raqam +998 bilan boshlanishi kerak.

Annotation yaratamiz

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

import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = UzbekPhoneValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UzbekPhone {

    String message() default "Telefon raqam +998 bilan boshlanishi kerak";

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

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

Validator yozamiz

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

public class UzbekPhoneValidator implements ConstraintValidator<UzbekPhone, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isBlank()) {
            return true;
        }

        return value.matches("^\\+998\\d{9}$");
    }
}

Bu yerda:

+998901234567

to‘g‘ri.

901234567

xato.

DTO’da ishlatamiz

public record CustomerCreateRequest(

        @NotBlank(message = "Name majburiy")
        String name,

        @NotBlank(message = "Phone majburiy")
        @UzbekPhone
        String phone
) {
}

Nega validator’da null uchun true qaytardik?

Chunki null yoki bo‘shlikni @NotBlank tekshiradi. @UzbekPhone faqat formatni tekshiradi.


17. Validation Groups

Ba’zan bitta DTO create va update’da har xil qoida bilan ishlatiladi.

Masalan:

public interface CreateGroup {
}

public interface UpdateGroup {
}

DTO:

public record ProductRequest(

        @NotNull(groups = UpdateGroup.class, message = "Id update uchun majburiy")
        Long id,

        @NotBlank(groups = CreateGroup.class, message = "Name create uchun majburiy")
        @Size(min = 2, max = 100, message = "Name 2 dan 100 tagacha bo‘lishi kerak")
        String name,

        @NotNull(groups = CreateGroup.class, message = "Price create uchun majburiy")
        @Positive(message = "Price 0 dan katta bo‘lishi kerak")
        BigDecimal price
) {
}

Controller:

@PostMapping
public ProductResponse create(
        @Validated(CreateGroup.class) @RequestBody ProductRequest request
) {
    return productService.create(request);
}

@PutMapping
public ProductResponse update(
        @Validated(UpdateGroup.class) @RequestBody ProductRequest request
) {
    return productService.update(request);
}

Lekin Java developer uchun amaliy tavsiya:

Validation groupsni bilib qo‘ying.
Lekin ko‘p holatda alohida CreateRequest va UpdateRequest yozish ancha oson va toza.

Ya’ni:

ProductCreateRequest
ProductUpdateRequest

ko‘pincha yaxshiroq.


18. Entity’da validation yozsa bo‘ladimi?

Texnik jihatdan bo‘ladi:

@Entity
public class User {

    @NotBlank
    private String fullName;
}

Lekin API request validation uchun DTO’da yozish yaxshiroq.

Nega?

Chunki entity - database modeli.

DTO - request/response modeli.

Create va update qoidalari har xil bo‘lishi mumkin.

Masalan:

Create’da password majburiy.
Update’da password majburiy emas.

Agar hammasini entity’ga yozsangiz, keyin chalkashib ketadi.

Amaliy qoida:

API validation → DTO
Database constraints → Entity yoki migration SQL
Business validation → Service

19. Database constraint validationdan alohidami?

Ha, alohida.

DTO validation:

@NotBlank
@Email

bu request bosqichida ishlaydi.

Database constraint:

@Column(nullable = false, unique = true)
private String email;

yoki SQL migration:

alter table users
add constraint uq_users_email unique (email);

Bu database bosqichida ishlaydi.

Ikkalasi ham kerak bo‘lishi mumkin.

Nega?

Chunki DTO validation foydalanuvchiga chiroyli xato qaytaradi.

Database constraint esa oxirgi himoya. Agar koddagi xato sabab duplicate email o‘tib ketsa ham, database ushlab qoladi.


20. Real Product validation to‘liq misol

ProductCreateRequest

public record ProductCreateRequest(

        @NotBlank(message = "Name majburiy")
        @Size(min = 2, max = 100, message = "Name 2 dan 100 tagacha bo‘lishi kerak")
        String name,

        @NotNull(message = "Price majburiy")
        @Positive(message = "Price 0 dan katta bo‘lishi kerak")
        BigDecimal price,

        @NotNull(message = "Quantity majburiy")
        @PositiveOrZero(message = "Quantity 0 yoki musbat bo‘lishi kerak")
        Integer quantity,

        @NotNull(message = "Category id majburiy")
        @Positive(message = "Category id musbat bo‘lishi kerak")
        Long categoryId
) {
}

ProductUpdateRequest

public record ProductUpdateRequest(

        @Size(min = 2, max = 100, message = "Name 2 dan 100 tagacha bo‘lishi kerak")
        String name,

        @Positive(message = "Price 0 dan katta bo‘lishi kerak")
        BigDecimal price,

        @PositiveOrZero(message = "Quantity 0 yoki musbat bo‘lishi kerak")
        Integer quantity,

        @Positive(message = "Category id musbat bo‘lishi kerak")
        Long categoryId
) {
}

Controller

@RestController
@RequestMapping("/api/products")
@Validated
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @PostMapping
    public ProductResponse create(
            @Valid @RequestBody ProductCreateRequest request
    ) {
        return productService.create(request);
    }

    @PutMapping("/{id}")
    public ProductResponse update(
            @PathVariable @Positive(message = "Id musbat bo‘lishi kerak") Long id,
            @Valid @RequestBody ProductUpdateRequest request
    ) {
        return productService.update(id, request);
    }

    @GetMapping
    public Page<ProductResponse> getAll(
            @RequestParam(defaultValue = "0") @Min(0) int page,
            @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size
    ) {
        return productService.getAll(page, size);
    }
}

Service

@Service
public class ProductService {

    private final ProductRepository productRepository;
    private final CategoryRepository categoryRepository;
    private final ProductMapper productMapper;

    public ProductService(ProductRepository productRepository,
                          CategoryRepository categoryRepository,
                          ProductMapper productMapper) {
        this.productRepository = productRepository;
        this.categoryRepository = categoryRepository;
        this.productMapper = productMapper;
    }

    @Transactional
    public ProductResponse create(ProductCreateRequest request) {
        if (productRepository.existsByName(request.name())) {
            throw new BadRequestException("Product name already exists");
        }

        Category category = categoryRepository.findById(request.categoryId())
                .orElseThrow(() -> new NotFoundException("Category not found"));

        Product product = productMapper.toEntity(request, category);
        Product savedProduct = productRepository.save(product);

        return productMapper.toResponse(savedProduct);
    }
}

Bu yerda validation ikki qismga bo‘lingan:

DTO validation:
- name bo‘sh emas
- price musbat
- quantity 0 yoki musbat
- categoryId null emas

Service validation:
- product name unique
- category database’da mavjud

21. Ko‘p uchraydigan xatolar

Xato 1: @Validni unutish

DTO’da annotation bor:

public record UserCreateRequest(
        @NotBlank String name
) {
}

Lekin controller’da @Valid yo‘q:

@PostMapping
public UserResponse create(@RequestBody UserCreateRequest request) {
    return userService.create(request);
}

Natija: validation ishlamaydi.

To‘g‘risi:

@PostMapping
public UserResponse create(@Valid @RequestBody UserCreateRequest request) {
    return userService.create(request);
}

Xato 2: String uchun @NotNull ishlatish

@NotNull
private String name;

Bu "" yoki " " ni ushlamaydi.

Yaxshiroq:

@NotBlank
private String name;

Xato 3: @Emailni yolg‘iz ishlatish

@Email
private String email;

Yaxshiroq:

@NotBlank
@Email
private String email;

Xato 4: Business validationni DTO annotation bilan qilishga urinish

Masalan:

Email database’da unique bo‘lishi kerak

Buni oddiy @Email yoki @NotBlank bilan hal qilib bo‘lmaydi.

Bu service’da tekshiriladi:

if (userRepository.existsByEmail(request.email())) {
    throw new BadRequestException("Email already exists");
}

Xato 5: Entity’ni request sifatida olish

Yomon:

@PostMapping
public ProductResponse create(@Valid @RequestBody Product product) {
    return productService.create(product);
}

Yaxshi:

@PostMapping
public ProductResponse create(@Valid @RequestBody ProductCreateRequest request) {
    return productService.create(request);
}

22. Java developer uchun qisqa qoida

DTO - input format validation
Controller - @Valid / @Validated ishlatadi
Service - business validation qiladi
Repository - database tekshiruvlari uchun ishlatiladi
Database - constraint bilan oxirgi himoya qiladi

23. Amaliy vazifa

Oldingi Product CRUD API loyihasiga validation qo‘shing.

Talablar

ProductCreateRequest:

name:
- majburiy
- 2 dan 100 gacha belgi

price:
- majburiy
- 0 dan katta

quantity:
- majburiy
- 0 yoki musbat

categoryId:
- majburiy
- musbat

ProductUpdateRequest:

name:
- optional
- kelsa 2 dan 100 gacha

price:
- optional
- kelsa 0 dan katta

quantity:
- optional
- kelsa 0 yoki musbat

categoryId:
- optional
- kelsa musbat

Controller:

POST /api/products → @Valid
PUT /api/products/{id} → @Valid + @Positive id
GET /api/products?page=0&size=20 → page >= 0, size 1..100

Global error response:

{
  "code": "VALIDATION_ERROR",
  "message": "Request data noto‘g‘ri",
  "fields": {
    "name": "Name majburiy",
    "price": "Price 0 dan katta bo‘lishi kerak"
  }
}

Xulosa

Validation Spring Boot’da noto‘g‘ri ma’lumotni erta ushlash uchun ishlatiladi.

Eng muhimlari:

@NotNull    → null bo‘lmasin
@NotBlank   → String bo‘sh bo‘lmasin
@NotEmpty   → collection/string bo‘sh bo‘lmasin
@Size       → uzunlik/size oralig‘i
@Email      → email format
@Min/@Max   → son oralig‘i
@Positive   → 0 dan katta
@Valid      → DTO validationni ishga tushiradi
@Validated  → method param va groups uchun

Eng muhim arxitektura qoida:

DTO validation - request formati uchun
Service validation - biznes qoida uchun
Database constraint - oxirgi himoya uchun