Exception Handling

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

Exception handling - Spring Boot’da xatolarni tartibli ushlash va clientga tushunarli response qaytarish usuli.

Bu bo‘lim ichida quyidagilar bor: @ControllerAdvice / @RestControllerAdvice, @ExceptionHandler, custom exception classes, ProblemDetail, consistent error response format.


1. Exception handling nima?

API ishlaganda har xil xatolar bo‘ladi:

User topilmadi
Email allaqachon mavjud
Validation xato
Database xato
Token noto‘g‘ri
Ruxsat yo‘q

Agar bu xatolarni tartibli ushlamasak, clientga noaniq response qaytadi:

{
  "timestamp": "2026-05-21T10:00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/api/users/99"
}

Bu frontend yoki mobile developer uchun yetarli emas.

Yaxshi API xatosi shunday bo‘lishi kerak:

{
  "code": "USER_NOT_FOUND",
  "message": "User topilmadi",
  "status": 404,
  "path": "/api/users/99"
}

2. Exception nima?

Java’da exception - dastur ishlayotgan paytda yuz beradigan xato.

Masalan:

User user = userRepository.findById(id)
        .orElseThrow(() -> new RuntimeException("User not found"));

Agar user topilmasa, exception tashlanadi.

throw new RuntimeException(...)

ya’ni: "bu holatda normal davom etib bo‘lmaydi".


3. Spring Boot’da default xato response

Agar siz exceptionni o‘zingiz ushlamasangiz, Spring Boot default error qaytaradi.

Masalan:

@GetMapping("/{id}")
public UserResponse getById(@PathVariable Long id) {
    throw new RuntimeException("User not found");
}

Response odatda shunga o‘xshash bo‘ladi:

{
  "timestamp": "2026-05-21T10:00:00.000+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/api/users/1"
}

Muammo:

status noto‘g‘ri: 500 emas, 404 bo‘lishi kerak
message aniq emas
frontend uchun code yo‘q
hamma xato bir xil formatda emas

Shuning uchun global exception handler yoziladi.


4. @RestControllerAdvice nima?

@RestControllerAdvice - butun project bo‘yicha exceptionlarni ushlaydigan global class.

@RestControllerAdvice
public class GlobalExceptionHandler {
}

Bu class ichida har xil exceptionlar uchun handler methodlar yoziladi.


5. @ExceptionHandler nima?

@ExceptionHandler - qaysi exceptionni ushlash kerakligini bildiradi.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ex.getMessage());
    }
}

Agar projectda RuntimeException chiqsa, Spring shu methodni chaqiradi.


6. Lekin hamma joyda RuntimeException ishlatish yomon

Yomonroq:

throw new RuntimeException("User not found");

Nega?

Chunki RuntimeException juda umumiy. U orqali xato turi aniq bilinmaydi.

Yaxshiroq:

throw new NotFoundException("User topilmadi");

Yoki:

throw new BadRequestException("Email allaqachon mavjud");

7. Custom exception classlar

Real loyihada o‘z exception classlaringiz bo‘lgani yaxshi.

NotFoundException

public class NotFoundException extends RuntimeException {

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

BadRequestException

public class BadRequestException extends RuntimeException {

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

ForbiddenException

public class ForbiddenException extends RuntimeException {

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

8. Service’da custom exception ishlatish

Masalan UserService:

@Service
public class UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;

    public UserService(UserRepository userRepository,
                       UserMapper userMapper) {
        this.userRepository = userRepository;
        this.userMapper = userMapper;
    }

    public UserResponse getById(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new NotFoundException("User topilmadi"));

        return userMapper.toResponse(user);
    }

    @Transactional
    public UserResponse create(UserCreateRequest request) {
        if (userRepository.existsByEmail(request.email())) {
            throw new BadRequestException("Email allaqachon mavjud");
        }

        User user = userMapper.toEntity(request);
        User savedUser = userRepository.save(user);

        return userMapper.toResponse(savedUser);
    }
}

Bu yerda:

User topilmasa → NotFoundException
Email mavjud bo‘lsa → BadRequestException

9. Error response DTO

Clientga bir xil formatda xato qaytarish uchun DTO yozamiz.

import java.time.LocalDateTime;
import java.util.Map;

public record ErrorResponse(
        String code,
        String message,
        int status,
        String path,
        LocalDateTime timestamp,
        Map<String, String> fields
) {
}

Bu yerda:

code       → xato kodi
message    → odam tushunadigan xabar
status     → HTTP status
path       → qaysi endpointda xato bo‘ldi
timestamp  → qachon xato bo‘ldi
fields     → validation xatolari uchun

10. GlobalExceptionHandler to‘liq misol

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(
            NotFoundException ex,
            HttpServletRequest request
    ) {
        ErrorResponse response = new ErrorResponse(
                "NOT_FOUND",
                ex.getMessage(),
                HttpStatus.NOT_FOUND.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }

    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleBadRequest(
            BadRequestException ex,
            HttpServletRequest request
    ) {
        ErrorResponse response = new ErrorResponse(
                "BAD_REQUEST",
                ex.getMessage(),
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

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

11. Natija qanday bo‘ladi?

Request:

GET /api/users/99

Agar user topilmasa:

{
  "code": "NOT_FOUND",
  "message": "User topilmadi",
  "status": 404,
  "path": "/api/users/99",
  "timestamp": "2026-05-21T15:30:12",
  "fields": null
}

Request:

POST /api/users

Agar email mavjud bo‘lsa:

{
  "code": "BAD_REQUEST",
  "message": "Email allaqachon mavjud",
  "status": 400,
  "path": "/api/users",
  "timestamp": "2026-05-21T15:32:44",
  "fields": null
}

12. Validation exception handling

Oldingi darsda @Valid ishlatdik.

Masalan request xato:

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

DTO:

public record UserCreateRequest(

        @NotBlank(message = "Full name majburiy")
        String fullName,

        @NotBlank(message = "Email majburiy")
        @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
) {
}

Validation xatosi uchun Spring MethodArgumentNotValidException tashlaydi.

Uni ushlaymiz:

import org.springframework.web.bind.MethodArgumentNotValidException;

import java.util.HashMap;
import java.util.Map;

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
        MethodArgumentNotValidException ex,
        HttpServletRequest request
) {
    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",
            HttpStatus.BAD_REQUEST.value(),
            request.getRequestURI(),
            LocalDateTime.now(),
            fields
    );

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

Response:

{
  "code": "VALIDATION_ERROR",
  "message": "Request data noto‘g‘ri",
  "status": 400,
  "path": "/api/users",
  "timestamp": "2026-05-21T15:35:01",
  "fields": {
    "fullName": "Full name majburiy",
    "email": "Email formati noto‘g‘ri",
    "age": "Yosh kamida 18 bo‘lishi kerak"
  }
}

13. RequestParam va PathVariable validation xatosi

Agar controller’da shunday yozgan bo‘lsak:

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

    @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=500

Bunda odatda ConstraintViolationException chiqadi.

Handler:

import jakarta.validation.ConstraintViolationException;

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraintViolation(
        ConstraintViolationException ex,
        HttpServletRequest request
) {
    Map<String, String> fields = new HashMap<>();

    ex.getConstraintViolations()
            .forEach(error -> {
                String field = error.getPropertyPath().toString();
                String message = error.getMessage();
                fields.put(field, message);
            });

    ErrorResponse response = new ErrorResponse(
            "VALIDATION_ERROR",
            "Request parameter noto‘g‘ri",
            HttpStatus.BAD_REQUEST.value(),
            request.getRequestURI(),
            LocalDateTime.now(),
            fields
    );

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

Response taxminan:

{
  "code": "VALIDATION_ERROR",
  "message": "Request parameter noto‘g‘ri",
  "status": 400,
  "path": "/api/products",
  "timestamp": "2026-05-21T15:40:20",
  "fields": {
    "getAll.page": "must be greater than or equal to 0",
    "getAll.size": "must be less than or equal to 100"
  }
}

14. Type mismatch xatolari

Masalan endpoint:

@GetMapping("/{id}")
public UserResponse getById(@PathVariable Long id) {
    return userService.getById(id);
}

Client noto‘g‘ri yubordi:

GET /api/users/abc

abc ni Long ga aylantirib bo‘lmaydi.

Bunda Spring odatda MethodArgumentTypeMismatchException tashlaydi.

import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatch(
        MethodArgumentTypeMismatchException ex,
        HttpServletRequest request
) {
    String message = "Parameter formati noto‘g‘ri: " + ex.getName();

    ErrorResponse response = new ErrorResponse(
            "TYPE_MISMATCH",
            message,
            HttpStatus.BAD_REQUEST.value(),
            request.getRequestURI(),
            LocalDateTime.now(),
            null
    );

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

Response:

{
  "code": "TYPE_MISMATCH",
  "message": "Parameter formati noto‘g‘ri: id",
  "status": 400,
  "path": "/api/users/abc",
  "timestamp": "2026-05-21T15:42:10",
  "fields": null
}

15. JSON noto‘g‘ri bo‘lsa

Client noto‘g‘ri JSON yuborishi mumkin:

{
  "name": "Laptop",
  "price": 
}

Bu JSON invalid.

Bunda Spring HttpMessageNotReadableException tashlaydi.

import org.springframework.http.converter.HttpMessageNotReadableException;

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleNotReadable(
        HttpMessageNotReadableException ex,
        HttpServletRequest request
) {
    ErrorResponse response = new ErrorResponse(
            "INVALID_JSON",
            "Request body noto‘g‘ri yoki JSON format xato",
            HttpStatus.BAD_REQUEST.value(),
            request.getRequestURI(),
            LocalDateTime.now(),
            null
    );

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

Response:

{
  "code": "INVALID_JSON",
  "message": "Request body noto‘g‘ri yoki JSON format xato",
  "status": 400,
  "path": "/api/products",
  "timestamp": "2026-05-21T15:45:00",
  "fields": null
}

16. Exception.class fallback handler

Har doim eng oxirida umumiy exception handler bo‘lgani yaxshi.

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneralException(
        Exception ex,
        HttpServletRequest request
) {
    ErrorResponse response = new ErrorResponse(
            "INTERNAL_SERVER_ERROR",
            "Serverda kutilmagan xato yuz berdi",
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            request.getRequestURI(),
            LocalDateTime.now(),
            null
    );

    return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(response);
}

Muhim: production’da clientga ex.getMessage()ni doim qaytarish xavfli bo‘lishi mumkin.

Yomon:

"message": ex.getMessage()

Chunki unda database, table, stack trace yoki ichki texnik ma’lumot chiqib ketishi mumkin.

Yaxshiroq:

"Serverda kutilmagan xato yuz berdi"

Lekin logga exceptionni yozish kerak.


17. Logging qo‘shish

Global handler ichida xatoni log qilish foydali.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log =
            LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneralException(
            Exception ex,
            HttpServletRequest request
    ) {
        log.error("Unexpected error. path={}", request.getRequestURI(), ex);

        ErrorResponse response = new ErrorResponse(
                "INTERNAL_SERVER_ERROR",
                "Serverda kutilmagan xato yuz berdi",
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(response);
    }
}

Clientga umumiy xabar qaytadi, lekin backend logda to‘liq stack trace qoladi.


18. To‘liq GlobalExceptionHandler

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log =
            LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(
            NotFoundException ex,
            HttpServletRequest request
    ) {
        ErrorResponse response = new ErrorResponse(
                "NOT_FOUND",
                ex.getMessage(),
                HttpStatus.NOT_FOUND.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }

    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleBadRequest(
            BadRequestException ex,
            HttpServletRequest request
    ) {
        ErrorResponse response = new ErrorResponse(
                "BAD_REQUEST",
                ex.getMessage(),
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

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

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex,
            HttpServletRequest request
    ) {
        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",
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                fields
        );

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

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ErrorResponse> handleConstraintViolation(
            ConstraintViolationException ex,
            HttpServletRequest request
    ) {
        Map<String, String> fields = new HashMap<>();

        ex.getConstraintViolations()
                .forEach(error ->
                        fields.put(
                                error.getPropertyPath().toString(),
                                error.getMessage()
                        )
                );

        ErrorResponse response = new ErrorResponse(
                "VALIDATION_ERROR",
                "Request parameter noto‘g‘ri",
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                fields
        );

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

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<ErrorResponse> handleTypeMismatch(
            MethodArgumentTypeMismatchException ex,
            HttpServletRequest request
    ) {
        ErrorResponse response = new ErrorResponse(
                "TYPE_MISMATCH",
                "Parameter formati noto‘g‘ri: " + ex.getName(),
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

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

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ErrorResponse> handleNotReadable(
            HttpMessageNotReadableException ex,
            HttpServletRequest request
    ) {
        ErrorResponse response = new ErrorResponse(
                "INVALID_JSON",
                "Request body noto‘g‘ri yoki JSON format xato",
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

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

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneralException(
            Exception ex,
            HttpServletRequest request
    ) {
        log.error("Unexpected error. path={}", request.getRequestURI(), ex);

        ErrorResponse response = new ErrorResponse(
                "INTERNAL_SERVER_ERROR",
                "Serverda kutilmagan xato yuz berdi",
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                request.getRequestURI(),
                LocalDateTime.now(),
                null
        );

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(response);
    }
}

19. ProblemDetail nima?

Spring 6'da ProblemDetail degan tayyor error response modeli bor.

U RFC 7807 uslubiga yaqin.

Oddiy misol:

import org.springframework.http.ProblemDetail;

@ExceptionHandler(NotFoundException.class)
public ProblemDetail handleNotFound(NotFoundException ex) {
    ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
    problem.setTitle("Resource not found");
    problem.setDetail(ex.getMessage());

    return problem;
}

Response taxminan:

{
  "type": "about:blank",
  "title": "Resource not found",
  "status": 404,
  "detail": "User topilmadi",
  "instance": "/api/users/99"
}

ProblemDetail foydali, chunki u standartga yaqin. Lekin boshlang'ich bosqichda avval o‘zingizning ErrorResponse formatini tushunib olish yaxshiroq.


20. @ResponseStatus bilan exception

Yana bir usul bor:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {

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

Bu usulda NotFoundException tashlansa, status 404 bo‘ladi.

Lekin kamchiligi:

Response formatni to‘liq nazorat qilish qiyinroq
Har xil xatolarni bir xil JSON formatda qaytarish noqulayroq

Shuning uchun real REST API’da @RestControllerAdvice ko‘proq ishlatiladi.


21. HTTP statuslarni to‘g‘ri tanlash

Holat

Status

Misol

Validation xato

400 Bad Request

name bo‘sh

Noto‘g‘ri request

400 Bad Request

email allaqachon mavjud

Login kerak

401 Unauthorized

token yo‘q

Ruxsat yo‘q

403 Forbidden

user admin emas

Resource topilmadi

404 Not Found

product id mavjud emas

Conflict

409 Conflict

duplicate unique value

Server xatosi

500 Internal Server Error

kutilmagan exception

Javachilar uchun eng ko‘p ishlatiladiganlari:

400
401
403
404
409
500

22. 400 va 404 farqi

400 Bad Request

Client noto‘g‘ri ma’lumot yubordi.

Misollar:

email format xato
age -5
price 0 dan kichik
JSON invalid

404 Not Found

Client so‘ragan narsa topilmadi.

Misollar:

/api/users/99 → bunday user yo‘q
/api/products/123 → bunday product yo‘q

23. 403 va 401 farqi

401 Unauthorized

User hali authentication qilmagan.

Masalan:

Token yo‘q
Token noto‘g‘ri
Token muddati tugagan

403 Forbidden

User login qilgan, lekin ruxsati yo‘q.

Masalan:

Oddiy user admin endpointga kirmoqchi

24. Product API’da exception handling misoli

ProductService

@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(readOnly = true)
    public ProductResponse getById(Long id) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new NotFoundException("Product topilmadi"));

        return productMapper.toResponse(product);
    }

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

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

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

        return productMapper.toResponse(saved);
    }

    @Transactional
    public ProductResponse update(Long id, ProductUpdateRequest request) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new NotFoundException("Product topilmadi"));

        if (request.name() != null && productRepository.existsByName(request.name())) {
            throw new BadRequestException("Product name allaqachon mavjud");
        }

        if (request.categoryId() != null) {
            Category category = categoryRepository.findById(request.categoryId())
                    .orElseThrow(() -> new NotFoundException("Category topilmadi"));

            product.setCategory(category);
        }

        productMapper.updateEntity(product, request);

        return productMapper.toResponse(product);
    }

    @Transactional
    public void delete(Long id) {
        if (!productRepository.existsById(id)) {
            throw new NotFoundException("Product topilmadi");
        }

        productRepository.deleteById(id);
    }
}

25. Exceptionlarni package qilish

Package-by-feature bo‘lsa:

src/main/java/com/example/demo
 ├── product
 │    ├── ProductController.java
 │    ├── ProductService.java
 │    └── ...
 ├── category
 │    └── ...
 └── common
      ├── exception
      │    ├── BadRequestException.java
      │    ├── NotFoundException.java
      │    └── GlobalExceptionHandler.java
      └── response
           └── ErrorResponse.java

common ichida butun projectga umumiy classlar turadi.


26. Eng ko‘p qilinadigan xatolar

Xato 1: Controller’da try-catch yozish

Yomon:

@GetMapping("/{id}")
public ResponseEntity<?> getById(@PathVariable Long id) {
    try {
        return ResponseEntity.ok(productService.getById(id));
    } catch (Exception ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }
}

Bu yomon, chunki har bir controller’da bir xil try-catch ko‘payib ketadi.

Yaxshi:

@GetMapping("/{id}")
public ProductResponse getById(@PathVariable Long id) {
    return productService.getById(id);
}

Exceptionni GlobalExceptionHandler ushlaydi.


Xato 2: Har joyda RuntimeException

Yomon:

throw new RuntimeException("Product not found");

Yaxshi:

throw new NotFoundException("Product topilmadi");

Xato 3: Hamma xatoga 500 qaytarish

Yomon:

{
  "status": 500,
  "message": "User not found"
}

User not found - server xatosi emas. To‘g‘ri status:

404 Not Found

Xato 4: Clientga stack trace qaytarish

Yomon:

{
  "message": "could not execute statement; SQL [insert into users...]"
}

Bu xavfli. Ichki texnik ma’lumot chiqib ketadi.

Yaxshi:

{
  "code": "INTERNAL_SERVER_ERROR",
  "message": "Serverda kutilmagan xato yuz berdi"
}

Xato 5: Error response format har xil bo‘lishi

Yomon:

"User not found"

Boshqa endpointda:

{
  "error": "Email exists"
}

Yana boshqasida:

{
  "message": "Validation failed",
  "errors": [...]
}

Yaxshi: hammasi bir xil formatda:

{
  "code": "...",
  "message": "...",
  "status": 400,
  "path": "...",
  "timestamp": "...",
  "fields": {}
}

27. Javachi uchun qisqa qoida

Service exception tashlaydi.
Controller try-catch qilmaydi.
GlobalExceptionHandler exceptionni ushlaydi.
Clientga bir xil ErrorResponse qaytadi.
Logga texnik xato yoziladi.
Clientga xavfsiz xabar qaytariladi.

28. Amaliy vazifa

Oldingi Product CRUD API loyihasiga exception handling qo‘shing.

Classlar

common.exception.NotFoundException
common.exception.BadRequestException
common.exception.GlobalExceptionHandler
common.response.ErrorResponse

ErrorResponse

public record ErrorResponse(
        String code,
        String message,
        int status,
        String path,
        LocalDateTime timestamp,
        Map<String, String> fields
) {
}

Handlerlar

Quyidagilarni ushlang:

NotFoundException → 404
BadRequestException → 400
MethodArgumentNotValidException → 400
ConstraintViolationException → 400
MethodArgumentTypeMismatchException → 400
HttpMessageNotReadableException → 400
Exception → 500

ProductService’da ishlating

Product topilmasa → NotFoundException
Category topilmasa → NotFoundException
Product name duplicate bo‘lsa → BadRequestException

Kutilgan response

{
  "code": "NOT_FOUND",
  "message": "Product topilmadi",
  "status": 404,
  "path": "/api/products/99",
  "timestamp": "2026-05-21T15:55:00",
  "fields": null
}

Xulosa

Exception handling - professional REST API yozishning majburiy qismi.

Eng muhimlari:

@RestControllerAdvice → global error handler
@ExceptionHandler → exception turini ushlaydi
Custom exception → xato turini aniq qiladi
ErrorResponse → clientga bir xil format
MethodArgumentNotValidException → @Valid xatolari
ConstraintViolationException → @RequestParam / @PathVariable validation
ProblemDetail → Spring 6 standart error modeli

Eng asosiy flow:

Service’da xato yuz berdi
        ↓
Custom exception tashlandi
        ↓
GlobalExceptionHandler ushladi
        ↓
ErrorResponse qaytdi
        ↓
Client tushunarli xato oldi