← Назад к вопросам
Как хендлить ошибки пользователя и отправлять ему необходимый результат вместо ошибки в Spring
1.8 Middle🔥 251 комментариев
#REST API и микросервисы#Spring Framework#Безопасность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как хендлить ошибки пользователя и отправлять ему необходимый результат вместо ошибки в Spring
Правильная обработка ошибок — критично для пользовательского опыта. В Spring есть несколько способов красиво обрабатывать исключения и возвращать понятные ошибки вместо stack trace.
1. Custom Exception классы
Сначала определи собственные исключения:
public class ApiException extends RuntimeException {
private final String errorCode;
private final int httpStatus;
public ApiException(String message, String errorCode, int httpStatus) {
super(message);
this.errorCode = errorCode;
this.httpStatus = httpStatus;
}
public String getErrorCode() {
return errorCode;
}
public int getHttpStatus() {
return httpStatus;
}
}
public class UserNotFoundException extends ApiException {
public UserNotFoundException(String userId) {
super("User not found: " + userId, "USER_NOT_FOUND", 404);
}
}
public class InvalidInputException extends ApiException {
public InvalidInputException(String message) {
super(message, "INVALID_INPUT", 400);
}
}
2. Unified Error Response DTO
Создай стандартный формат ответа об ошибке:
public class ErrorResponse {
private String errorCode;
private String message;
private long timestamp;
private String path;
private Map<String, String> details;
public ErrorResponse(String errorCode, String message, String path) {
this.errorCode = errorCode;
this.message = message;
this.timestamp = System.currentTimeMillis();
this.path = path;
}
// Getters и setters
public String getErrorCode() { return errorCode; }
public String getMessage() { return message; }
public long getTimestamp() { return timestamp; }
public String getPath() { return path; }
public Map<String, String> getDetails() { return details; }
public void setDetails(Map<String, String> details) {
this.details = details;
}
}
3. Global Exception Handler с @ControllerAdvice
Основной способ обработки всех ошибок приложения:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(
UserNotFoundException ex,
WebRequest request) {
ErrorResponse response = new ErrorResponse(
ex.getErrorCode(),
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(response,
HttpStatus.valueOf(ex.getHttpStatus()));
}
@ExceptionHandler(InvalidInputException.class)
public ResponseEntity<ErrorResponse> handleInvalidInput(
InvalidInputException ex,
WebRequest request) {
ErrorResponse response = new ErrorResponse(
ex.getErrorCode(),
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex,
WebRequest request) {
ErrorResponse response = new ErrorResponse(
"INTERNAL_ERROR",
"An unexpected error occurred",
request.getDescription(false).replace("uri=", "")
);
ex.printStackTrace();
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
4. Валидация с @Valid и BindingResult
Для обработки ошибок валидации входных данных:
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import org.springframework.validation.BindingResult;
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<?> createUser(
@Valid @RequestBody CreateUserRequest request,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>();
bindingResult.getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
ErrorResponse response = new ErrorResponse(
"VALIDATION_ERROR",
"Input validation failed",
"/api/users"
);
response.setDetails(errors);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(new UserDto(), HttpStatus.CREATED);
}
}
5. DTO для валидации
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
public class CreateUserRequest {
@NotBlank(message = "Name is required")
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 и setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
6. Пример использования в сервисе
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserDto getUserById(String userId) {
return userRepository.findById(userId)
.map(user -> new UserDto(user))
.orElseThrow(() -> new UserNotFoundException(userId));
}
public UserDto createUser(CreateUserRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new InvalidInputException("Email already exists");
}
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setAge(request.getAge());
User saved = userRepository.save(user);
return new UserDto(saved);
}
}
Важные моменты
- Не отправляй stack trace пользователю — это уязвимость безопасности
- Используй логирование — логируй полные ошибки в бэкенде для отладки
- Стандартный формат — всегда возвращай один формат для ошибок
- HTTP статусы — используй правильные статусы (400 для валидации, 404 для not found, 500 для server errors)
- Информативные сообщения — помогай пользователю понять, что пошло не так
Рекомендация: используй @ControllerAdvice для глобальной обработки всех исключений, @Valid для валидации входных данных, и собственные исключения для бизнес-логики.