Помилки в додатку —, на жаль, річ неминуча. Некоректні вхідні дані від користувача, збої запитів до бази даних, відмова зовнішнього сервісу — усе це стандартні ситуації, які можуть призвести до виключень. Для розробника важливо вміти грамотно обробляти ці помилки, щоб вони не «виливалися» на кінцевого користувача у вигляді лякальних stack-trace'ів.
У Spring MVC для цього є вбудовані механізми:
- Локальна обробка помилок: за допомогою методів у контролерах.
- Глобальна обробка помилок: з використанням анотації
@ControllerAdvice.
Обробники помилок допоможуть:
- Сховати внутрішню інформацію (наприклад, stack-trace'и).
- Показати користувачу зрозуміле й корисне повідомлення про помилку.
- Сформувати відповідний HTTP-статус код (наприклад,
400 Bad Requestабо500 Internal Server Error). - Вести централізоване логування помилок.
Локальна обробка помилок
Локальна обробка помилок — це обробка виключень у межах одного контролера. У Spring для цього використовується анотація @ExceptionHandler. Вона дозволяє зв'язати виключення з методом, який має бути викликаний, якщо це виключення виникне.
Приклад локального обробника Розглянемо приклад, у якому ми обробляємо виключення MethodArgumentNotValidException, що виникає під час валідації вхідних даних.
@RestController
@RequestMapping("/api/example")
public class ExampleController {
@PostMapping("/create")
public String createExample(@Valid @RequestBody ExampleDto dto) {
// Твій код обробки запиту
return "Example created successfully!";
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException exception) {
// Дістаємо першу помилку валідації, щоб повернути її користувачу
String errorMessage = exception.getBindingResult()
.getFieldErrors()
.get(0)
.getDefaultMessage();
return ResponseEntity.badRequest().body("Validation Error: " + errorMessage);
}
}
У цьому коді:
- Якщо при валідації об'єкта
ExampleDtoбуде знайдено порушення (наприклад, полеnameпорожнє), то кине виключенняMethodArgumentNotValidException. - Обробник
@ExceptionHandlerперехопить його й поверне користувачу зрозуміле повідомлення про помилку.
Локальна обробка підходить для невеликих проєктів, але стає недостатньою, якщо в системі багато контролерів, і вони мають однаково реагувати на ті самі виключення. У таких випадках зручно використовувати глобальну обробку помилок.
Глобальна обробка помилок
Глобальна обробка помилок за допомогою анотації @ControllerAdvice дозволяє визначити єдиний набір обробників виключень, які застосовуються до всіх контролерів у додатку.
@ControllerAdvice — це спеціальна анотація Spring, яка дозволяє створити централізований обробник виключень. Усі методи, анотовані @ExceptionHandler всередині класу з @ControllerAdvice, будуть викликані для обробки виключень, що виникли в будь-якому контролері додатку.
Приклад глобального обробника Створимо клас для обробки помилок:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException exception) {
// Збираємо повідомлення про всі помилки валідації
String errorMessage = exception.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.badRequest().body("Validation Errors: " + errorMessage);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception exception) {
// Логуємо помилку (в реальному проєкті краще використовувати логгер)
System.err.println("Unexpected error: " + exception.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An unexpected error occurred. Please try again later.");
}
}
У цьому коді:
- Метод
handleValidationExceptionобробляє тільки помилки валідації (MethodArgumentNotValidException) і повертає зрозуміле повідомлення, яке агрегує всі помилки. - Метод
handleGeneralExceptionперехоплює будь-які інші виключення й повертає повідомлення про внутрішню помилку сервера.
Як це працює?
Коли виникає виключення, Spring спочатку перевіряє, чи є локальний обробник у поточному контролері. Якщо його немає, він шукає підходящий метод у класі з анотацією @ControllerAdvice.
Переваги глобальної обробки помилок
Використання @ControllerAdvice має кілька переваг:
- Централізоване керування обробкою виключень.
- Повторне використання коду (не треба дублювати логіку обробки помилок у кожному контролері).
- Можливість використовувати поліморфізм (можна створити ієрархію виключень і обробляти їх по черзі).
Приклад: обробка кастомних виключень
У реальних проєктах часто створюють кастомні виключення для специфічних ситуацій. Наприклад, виключення ResourceNotFoundException для випадків, коли сутність не знайдена.
Створимо клас виключення:
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Обробка виключення Додамо обробник у GlobalExceptionHandler:
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException exception) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
}
Використання в контролері
Тепер можна кидати виключення ResourceNotFoundException, якщо ресурс не знайдено:
@GetMapping("/item/{id}")
public Item getItem(@PathVariable Long id) {
return itemRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Item with ID " + id + " not found"));
}
Якщо елемент із вказаним id відсутній у базі, користувач отримає відповідь з кодом 404 Not Found і повідомлення "Item with ID {id} not found".
Практичні поради
- При створенні кастомних виключень уникай "роздмухування" ієрархії класів. Використовуй чітку й лаконічну структуру.
- Завжди логуй помилки, але не показуй користувачу внутрішні подробиці (наприклад, SQL-запити або stack-trace'и).
- Встановлюй відповідні HTTP-статус коди в відповідях (наприклад, 400 для помилок валідації, 404 для відсутнього ресурсу, 500 для внутрішніх помилок).
- Додавай причину помилки в опис відповіді, щоб полегшити діагностику проблем.
Практичне завдання
Завдання: реалізуй глобальний обробник помилок, який:
- Обробляє помилки валідації (
MethodArgumentNotValidException) і повертає агрегований список помилок. - Обробляє випадки, коли ресурс не знайдено (
ResourceNotFoundException). - Ловить усі інші виключення й повертає стандартне повідомлення "Internal Server Error".
Спробуй інтегрувати цей обробник у свій поточний проєкт і протестувати різні сценарії помилок.
На цьому етапі твій код стане більш надійним: ти зможеш показувати користувачу корисну інформацію, а не «гарний» stack-trace з помилками. Тепер будь-яке виключення в системі буде оброблене елегантно й централізовано.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ