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 manfiyShu 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 requestma’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> itemsBu 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=1000Bu 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 validationuchun 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
@PositiveBusiness 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 emasmiBular 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:
+998901234567to‘g‘ri.
901234567xato.
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
ProductUpdateRequestko‘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 → Service19. Database constraint validationdan alohidami?
Ha, alohida.
DTO validation:
@NotBlank
@Emailbu 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 mavjud21. 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 kerakBuni 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 qiladi23. 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
- musbatProductUpdateRequest:
name:
- optional
- kelsa 2 dan 100 gacha
price:
- optional
- kelsa 0 dan katta
quantity:
- optional
- kelsa 0 yoki musbat
categoryId:
- optional
- kelsa musbatController:
POST /api/products → @Valid
PUT /api/products/{id} → @Valid + @Positive id
GET /api/products?page=0&size=20 → page >= 0, size 1..100Global 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 uchunEng muhim arxitektura qoida:
DTO validation - request formati uchun
Service validation - biznes qoida uchun
Database constraint - oxirgi himoya uchun