Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Bean Validation

Нельзя доверять данным, которые прислал клиент: заголовок может быть пустым, текст — из одной буквы, номер — отрицательным. Bean Validation позволяет описать правила аннотациями прямо на полях, а проверку выполнит сервер. В Jakarta REST она подключена автоматически: достаточно попросить проверить аргумент метода.

Правила на записи

Добавьте ограничения в src/main/java/com/example/blog/NewPost.java:

// src/main/java/com/example/blog/NewPost.java
package com.example.blog;
 
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
 
public record NewPost(
        @NotBlank(message = "Заголовок обязателен")
        @Size(max = 100, message = "Заголовок не длиннее 100 символов")
        String title,
 
        @NotBlank(message = "Текст обязателен")
        @Size(min = 10, message = "Текст не короче 10 символов")
        String body) {
}

Без атрибута message используется стандартное сообщение на языке сервера, например must not be blank.

Включаем проверку

В ресурсе пометьте аргумент аннотацией @Valid, а параметр пути — ограничением @Min:

// src/main/java/com/example/blog/PostResource.java (фрагмент)
@GET
@Path("/{id}")
public Post show(@PathParam("id") @Min(value = 1, message = "Номер записи должен быть больше нуля") long id) {
    // ...
}
 
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response create(@Valid NewPost data, @Context UriInfo uriInfo) {
    // ...
}

Импорты — jakarta.validation.Valid и jakarta.validation.constraints.Min. Если данные нарушают правила, метод даже не вызывается: Jakarta REST сразу отвечает 400.

curl -i -H "Content-Type: application/json" -d '{"title":"","body":"коротко"}' http://localhost:8080/blog/api/posts
HTTP/1.1 400 Bad Request
Content-Type: text/plain;charset=UTF-8
validation-exception: true

[PARAMETER][create.arg0.title][Заголовок обязателен][][PARAMETER][create.arg0.body][Текст не короче 10 символов][коротко]

Такой ответ формирует RESTEasy — реализация Jakarta REST в Open Liberty. Для людей он неудобен, поэтому сделаем свой.

Ответ в JSON

Ошибки валидации приходят как исключение ConstraintViolationException, а исключения мы уже умеем превращать в ответы:

// src/main/java/com/example/blog/ValidationMapper.java
package com.example.blog;
 
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import java.util.List;
 
@Provider
public class ValidationMapper implements ExceptionMapper<ConstraintViolationException> {
 
    public record FieldError(String field, String message) {
    }
 
    @Override
    public Response toResponse(ConstraintViolationException e) {
        List<FieldError> errors = e.getConstraintViolations().stream()
                .map(v -> new FieldError(lastName(v), v.getMessage()))
                .toList();
        return Response.status(Response.Status.BAD_REQUEST)
                .type(MediaType.APPLICATION_JSON)
                .entity(errors)
                .build();
    }
 
    private static String lastName(ConstraintViolation<?> violation) {
        String name = null;
        for (Path.Node node : violation.getPropertyPath()) {
            name = node.getName();
        }
        return name;
    }
}

Путь к полю выглядит как create.arg0.title; метод lastName берёт из него последнее имя — title. Тот же запрос теперь возвращает (порядок ошибок не гарантирован):

HTTP/1.1 400 Bad Request
Content-Type: application/json

[{"field":"title","message":"Заголовок обязателен"},{"field":"body","message":"Текст не короче 10 символов"}]

Основные ограничения

АннотацияЧто проверяет
@NotNullЗначение не null
@NotBlankСтрока не пустая и не из одних пробелов
@Size(min, max)Длина строки или размер коллекции
@Min, @MaxГраницы числа
@EmailПохоже на адрес почты
@Pattern(regexp)Совпадение с регулярным выражением
@Past, @FutureДата в прошлом или будущем

Частая ошибка

Поставить ограничения на поля, но забыть @Valid у аргумента метода. Тогда проверки не будет вообще, и пустая запись спокойно сохранится:

HTTP/1.1 201 Created

{"body":"коротко","createdAt":"2026-10-04","id":3,"title":""}

Аннотации на полях только описывают правила, а @Valid говорит, где их проверять.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.