← Назад к вопросам
Что такое валидация на уровне контроллера?
2.0 Middle🔥 191 комментариев
#SOLID и паттерны проектирования#Spring Boot и Spring Data
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Валидация на уровне контроллера
Валидация на уровне контроллера — это проверка корректности входных данных, которая происходит в слое контроллера (в точке входа запроса в приложение). Это первая линия защиты, которая гарантирует, что только валидные данные попадают в бизнес-логику приложения.
Архитектура валидации
В чистой архитектуре валидация происходит на нескольких уровнях:
- Контроллер — базовая валидация входных параметров
- DTO (Data Transfer Object) — декларативная валидация через аннотации
- Domain / Service — бизнес-логика валидация (invariants)
- База данных — constraints на уровне схемы
Стандартный способ с использованием Bean Validation
import javax.validation.constraints.*;
public class UserRegistrationRequest {
@NotBlank(message = "Username не может быть пустым")
@Size(min = 3, max = 50, message = "Username должен быть от 3 до 50 символов")
private String username;
@NotBlank(message = "Email не может быть пустым")
@Email(message = "Email должен быть корректным")
private String email;
@NotBlank(message = "Пароль не может быть пустым")
@Size(min = 8, message = "Пароль должен быть минимум 8 символов")
@Pattern(
regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)[A-Za-z\\d]{8,}$",
message = "Пароль должен содержать заглавные, строчные буквы и цифры"
)
private String password;
@NotNull(message = "Возраст не может быть null")
@Min(value = 18, message = "Возраст должен быть минимум 18")
@Max(value = 120, message = "Возраст должен быть максимум 120")
private Integer age;
// Getters, setters
public String getUsername() { return username; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public Integer getAge() { return age; }
}
Использование в Spring контроллере
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/register")
public ResponseEntity<UserResponse> register(@Valid @RequestBody UserRegistrationRequest request) {
// Валидация происходит АВТОМАТИЧЕСКИ перед вызовом метода
// Если данные невалидны, Spring вернёт 400 Bad Request с ошибками
UserResponse response = userService.registerUser(request);
return ResponseEntity.status(201).body(response);
}
}
Обработка ошибок валидации
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(
error.getField(),
error.getDefaultMessage()
)
);
return ResponseEntity
.status(400)
.body(new ErrorResponse(
"Validation failed",
400,
errors
));
}
}
Пример ответа при ошибке валидации
{
"message": "Validation failed",
"status": 400,
"errors": {
"username": "Username должен быть от 3 до 50 символов",
"email": "Email должен быть корректным",
"password": "Пароль должен содержать заглавные, строчные буквы и цифры",
"age": "Возраст должен быть минимум 18"
}
}
Кастомная валидация в контроллере
@PostMapping("/create-order")
public ResponseEntity<OrderResponse> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
// Базовая валидация через @Valid выполнена
// Дополнительная кастомная валидация
if (request.getDeliveryDate().isBefore(LocalDate.now())) {
throw new BadRequestException("Дата доставки не может быть в прошлом");
}
if (request.getItems().isEmpty()) {
throw new BadRequestException("Заказ должен содержать минимум один товар");
}
OrderResponse response = orderService.createOrder(request);
return ResponseEntity.status(201).body(response);
}
Кастомные аннотации валидации
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.*;
// Аннотация
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface ValidPhoneNumber {
String message() default "Некорректный номер телефона";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// Реализация валидатора
public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // @NotNull обработает null
}
// Проверка формата номера телефона
return value.matches("^\\+?[1-9]\\d{1,14}$"); // E.164 формат
}
}
// Использование
public class ContactRequest {
@NotBlank
@ValidPhoneNumber
private String phoneNumber;
}
Валидация коллекций
import javax.validation.constraints.NotEmpty;
public class BatchUserRequest {
@NotEmpty(message = "Список пользователей не может быть пустым")
@Valid // Валидирует каждый элемент списка
private List<UserRegistrationRequest> users;
public List<UserRegistrationRequest> getUsers() { return users; }
}
@PostMapping("/batch-register")
public ResponseEntity<List<UserResponse>> batchRegister(
@Valid @RequestBody BatchUserRequest request) {
// Валидируется весь список и каждый объект в списке
List<UserResponse> responses = userService.registerBatch(request.getUsers());
return ResponseEntity.status(201).body(responses);
}
Валидация Query параметров
@GetMapping("/users")
public ResponseEntity<List<UserResponse>> getUsers(
@RequestParam(required = false)
@Min(value = 1, message = "Страница должна быть >= 1")
Integer page,
@RequestParam(required = false)
@Min(value = 1, message = "Размер страницы должен быть >= 1")
@Max(value = 100, message = "Размер страницы не может быть > 100")
Integer size,
@RequestParam(required = false)
@Pattern(regexp = "^(name|date|id)$", message = "Некорректное поле для сортировки")
String sortBy) {
return ResponseEntity.ok(userService.getUsers(page, size, sortBy));
}
Валидация Path параметров
@GetMapping("/users/{id}")
public ResponseEntity<UserResponse> getUser(
@PathVariable
@Min(value = 1, message = "ID должен быть положительным числом")
Long id) {
UserResponse response = userService.getUserById(id);
return ResponseEntity.ok(response);
}
Niveles валидации
// Группы валидации для разных сценариев
public class ValidationGroups {
public interface Create {}
public interface Update {}
}
public class UserRequest {
@NotBlank(groups = {ValidationGroups.Create.class})
@Null(groups = {ValidationGroups.Update.class})
private Long id;
@NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class})
private String email;
}
@PostMapping
public ResponseEntity<UserResponse> create(
@Valid(groups = ValidationGroups.Create.class)
@RequestBody UserRequest request) {
// Валидирует в соответствии с группой Create
return ResponseEntity.status(201).body(userService.create(request));
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> update(
@PathVariable Long id,
@Valid(groups = ValidationGroups.Update.class)
@RequestBody UserRequest request) {
// Валидирует в соответствии с группой Update
return ResponseEntity.ok(userService.update(id, request));
}
Лучшие практики
- Используй @Valid/@Validated для автоматической валидации
- Декларативная валидация через аннотации проще, чем программная
- Кастомные аннотации для повторяющейся валидации
- Группы валидации для разных сценариев (Create/Update)
- Детальные сообщения об ошибках помогают клиентам
- Обрабатывай исключения в GlobalExceptionHandler
- Не полагайся только на контроллер — добавь валидацию на уровне domain
Валидация на уровне контроллера — это критичный слой, который предотвращает попадание некорректных данных в остальную систему и обеспечивает безопасность приложения.