Можно ли использовать аннотацию для проверки параметров метода, чтобы избежать NullPointerException?
Что проверяют
Понимаете ли вы, что аннотация сама по себе ничего не проверяет: её должен кто-то прочитать, в рантайме или при анализе кода. Следом спрашивают, что конкретно произойдёт при передаче null и почему проверка молчит при вызове метода из того же класса.
Как отвечать
Честная формулировка: аннотация не избавляет от ошибки, а переносит её раньше и делает понятнее. Вместо NPE в глубине метода вы получаете внятное исключение на входе или предупреждение ещё в IDE. Вариантов три:
- Bean Validation в рантайме:
jakarta.validation.constraints.NotNullна параметре (в Spring Boot 3 и новее пакетjakarta.*, а неjavax.*), зависимостьspring-boot-starter-validation, на классе@Validated. Spring оборачивает бин в прокси, и приnullметод даже не начнёт выполняться: вылетитConstraintViolationException; - Lombok
@NonNull: генерирует проверку в начале метода и бросает всё тот жеNullPointerException, но сразу и с именем параметра в сообщении; - статический анализ: JSpecify
@Nullable/@NonNullплюс NullAway или инспекции IntelliJ IDEA. Ошибку ловит сборка или CI ещё до запуска. Spring Framework 7 перешёл на аннотации JSpecify, так что null-контракты его API видны прямо в IDE.
Без аннотаций работает Objects.requireNonNull(value, "value"): быстрый отказ на входе в публичный метод. А в современных JDK сообщение NPE само называет, какая именно ссылка оказалась null.
Для значения, которого может не быть, лучше вернуть из метода Optional, чем принимать null и проверять его в каждом месте.
Где спотыкаются
Ставят @NotNull на параметр и считают, что готово. Без @Validated на классе и без Spring-прокси аннотация остаётся меткой, которую никто не читает.
Вызывают валидируемый метод из соседнего метода того же класса. Вызов идёт мимо прокси, проверка не срабатывает. Та же ловушка, что у @Transactional.
Путают validator.validate(obj) с проверкой аргументов. Он проверяет поля объекта, а для параметров метода нужен validator.forExecutables().validateParameters(...). И не забывают обработать ConstraintViolationException: без своего обработчика клиент получит 500 вместо 400.